diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..66a9cbc3 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,21 @@ +name: Maven Deploy + +on: + push: + branches: + - master + +jobs: + deploy_package: + runs-on: ubuntu-latest + if: ${{ endsWith(github.event.head_commit.message, '-package') }} + steps: + - uses: actions/checkout@v1 + - name: ☕️Set up JDK 16 + uses: actions/setup-java@v1 + with: + java-version: 16 + - name: 📦Deploy to Github Package Registry + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: mvn --file ci-pom.xml deploy diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..7fc1c00a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,33 @@ +name: Release + +on: + release: + types: [ published ] + branches: [ master ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: 🛎 Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 # [!] we need to checkout with tags and commit history + + - name: 📋 Get Commits since last Release + id: changes + uses: simbo/changes-since-last-release-action@v1 + + - name: 📣 Output collected Data + run: | + echo "Changes since ${{ steps.changes.outputs.last-tag }}:" + echo "${{ steps.changes.outputs.log }}" + - name: 📝Edit Release + uses: irongut/EditRelease@v1.2.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + id: ${{ github.event.release.id }} + body: | + Changes since ```${{ steps.changes.outputs.last-tag }}``` + ${{ steps.changes.outputs.log }} diff --git a/ci-pom.xml b/ci-pom.xml new file mode 100644 index 00000000..9b39f06d --- /dev/null +++ b/ci-pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + us.mytheria + BlobLib + 1.692.0 + pom.xml + + bloblib + jar + BlobLib + + + 16 + UTF-8 + + + ${name}-${parent.version} + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-shade-plugin + 3.4.1 + + + shade + package + + shade + + + + + *:* + + META-INF/MANIFEST.MF + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + + + + + src/main/resources + true + + + + + scm:git:${project.scm.url} + scm:git:${project.scm.url} + git@github.com:idhub-io/idhub-api.git + HEAD + + \ No newline at end of file diff --git a/local-pom.xml b/local-pom.xml new file mode 100644 index 00000000..2fc296f3 --- /dev/null +++ b/local-pom.xml @@ -0,0 +1,134 @@ + + + 4.0.0 + + us.mytheria + BlobLib + 1.692.0 + pom.xml + + bloblib + jar + BlobLib + + + /Users/lbenav8095/Documents/Dev/Java/anjoismysign/1.19 Test Server + 16 + 16 + 16 + UTF-8 + + + ${name}-${parent.version} + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.3.1 + + + attach-javadocs + package + + jar + + + false + false + public + true + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.10.1 + + + org.apache.maven.plugins + maven-shade-plugin + 3.4.1 + + + shade + package + + shade + + + + + *:* + + META-INF/MANIFEST.MF + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + com.fasterxml.jackson + world.hunger.jackson + + + org.apache.commons + global.warming.commons + + + fr.skytasul.guardianbeam + nuclear.winter.guardianbeam + + + + false + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.4.0 + + + copy + package + + copy + + + + + + + ${project.groupId} + ${project.artifactId} + ${project.version} + jar + true + ${testServer}/plugins/ + ${build.finalName}.jar + + + + + + + + src/main/resources + true + + + + + scm:git:${project.scm.url} + scm:git:${project.scm.url} + git@github.com:idhub-io/idhub-api.git + HEAD + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 63ba49cd..b0e1fb47 100644 --- a/pom.xml +++ b/pom.xml @@ -1,124 +1,14 @@ - - + 4.0.0 - us.mytheria - bloblib - 1.603s - jar - - BlobLib - - - scm:git:git@github.com:anjoismysign/BlobLib.git - scm:git:git@github.com:anjoismysign/BlobLib.git - https://github.com/anjoismysign/BlobLib - 1.2.3 - - - - - github - GitHub anjoismysign Apache Maven Packages - https://maven.pkg.github.com/anjoismysign/BlobLib - - - - - /Users/lbenav8095/Documents/Dev/Java/anjoismysign/1.19.2 Wild North - 16 - 16 - 16 - UTF-8 - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.10.1 - - - org.apache.maven.plugins - maven-shade-plugin - 3.4.1 - - - shade - package - - shade - - - ${project.artifactId}-${project.version} - - - *:* - - META-INF/MANIFEST.MF - META-INF/*.SF - META-INF/*.DSA - META-INF/*.RSA - - - - - - com.fasterxml.jackson - world.hunger.jackson - - - org.apache.commons - global.warming.commons - - - fr.skytasul.guardianbeam - nuclear.winter.guardianbeam - - - - false - - - - - - org.apache.maven.plugins - maven-dependency-plugin - 3.4.0 - - - copy - package - - copy - - - - - - - ${project.groupId} - ${project.artifactId} - ${project.version} - jar - true - ${testServer}/plugins/ - BlobLib-${project.version}.jar - - - - - - - - src/main/resources - true - - - + BlobLib + 1.692.0 + pom + spigotmc-repo @@ -144,18 +34,17 @@ placeholderapi https://repo.extendedclip.com/content/repositories/placeholderapi/ + + md_5-public + https://repo.md-5.net/content/groups/public/ + + org.spigotmc - spigot-api - 1.19.3-R0.1-SNAPSHOT - provided - - - com.github.MilkBowl - VaultAPI - 1.7 + spigot + 1.20.1-R0.1-SNAPSHOT provided @@ -171,8 +60,8 @@ org.mongodb - mongo-java-driver - 3.12.11 + mongodb-driver-sync + 4.10.2 redis.clients @@ -194,33 +83,26 @@ com.github.decentsoftware-eu decentholograms - 2.7.2 + 2.8.0 provided io.github.skytasul guardianbeam - 2.3.1 + 2.3.2 compile net.md-5 bungeecord-api - 1.19-R0.1-SNAPSHOT + 1.20-R0.2-SNAPSHOT jar provided - - net.md-5 - bungeecord-api - 1.19-R0.1-SNAPSHOT - javadoc - provided - me.anjoismysign.anjo anjo - 0.3.9f + 0.3.9m compile @@ -229,5 +111,23 @@ 2.11.2 provided + + net.milkbowl.vault + vaultapi + 1.7.4 + + + LibsDisguises + LibsDisguises + 10.0.29 + provided + + + + github + GitHub anjoismysign Apache Maven Packages + https://maven.pkg.github.com/anjoismysign/BlobLib + + diff --git a/src/main/java/us/mytheria/bloblib/BlobLib.java b/src/main/java/us/mytheria/bloblib/BlobLib.java index e36ee174..67092129 100644 --- a/src/main/java/us/mytheria/bloblib/BlobLib.java +++ b/src/main/java/us/mytheria/bloblib/BlobLib.java @@ -1,22 +1,31 @@ package us.mytheria.bloblib; +import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; import us.mytheria.bloblib.command.BlobLibCmd; +import us.mytheria.bloblib.disguises.DisguiseManager; import us.mytheria.bloblib.enginehub.EngineHubManager; import us.mytheria.bloblib.entities.logger.BlobPluginLogger; import us.mytheria.bloblib.hologram.HologramManager; import us.mytheria.bloblib.managers.*; import us.mytheria.bloblib.managers.fillermanager.FillerManager; +import us.mytheria.bloblib.reflection.BlobReflectionLib; +import us.mytheria.bloblib.utilities.SerializationLib; import us.mytheria.bloblib.vault.VaultManager; /** * The main class of the plugin */ -public final class BlobLib extends JavaPlugin { +public class BlobLib extends JavaPlugin { private static BlobPluginLogger anjoLogger; + + private BlobLibUpdater bloblibupdater; + private BlobLibAPI api; + private ScriptManager scriptManager; private VaultManager vaultManager; private EngineHubManager engineHubManager; + private DisguiseManager disguiseManager; private HologramManager hologramManager; private BlobLibFileManager fileManager; private InventoryManager inventoryManager; @@ -30,6 +39,11 @@ public final class BlobLib extends JavaPlugin { private DropListenerManager dropListenerManager; private ColorManager colorManager; private PluginManager pluginManager; + private ActionManager actionManager; + private BlobLibConfigManager configManager; + private BlobLibListenerManager listenerManager; + private BlobReflectionLib reflectionLib; + private SerializationLib serializationLib; private static BlobLib instance; @@ -57,41 +71,68 @@ public static BlobPluginLogger getAnjoLogger() { @Override public void onEnable() { instance = this; + api = BlobLibAPI.getInstance(this); + bloblibupdater = new BlobLibUpdater(this); + reflectionLib = BlobReflectionLib.getInstance(this); + serializationLib = SerializationLib.getInstance(this); anjoLogger = new BlobPluginLogger(this); scriptManager = new ScriptManager(); pluginManager = new PluginManager(); colorManager = new ColorManager(); fileManager = new BlobLibFileManager(); fileManager.unpackYamlFile("/BlobInventory", "CurrencyBuilder", false); - + inventoryManager = new InventoryManager(); messageManager = new MessageManager(); + actionManager = new ActionManager(); soundManager = new SoundManager(); fillerManager = new FillerManager(); vaultManager = new VaultManager(); engineHubManager = new EngineHubManager(); + disguiseManager = new DisguiseManager(); hologramManager = new HologramManager(); chatManager = new ChatListenerManager(); positionManager = new SelPosListenerManager(); selectorManager = new SelectorListenerManager(); variableSelectorManager = new VariableSelectorManager(); dropListenerManager = new DropListenerManager(); + configManager = BlobLibConfigManager.getInstance(this); + listenerManager = BlobLibListenerManager.getInstance(configManager); //Load reloadable managers reload(); new BlobLibCmd(); + + Bukkit.getScheduler().runTask(this, + () -> disguiseManager.load()); + } + + @Override + public void onDisable() { + serializationLib.shutdown(); } /** * Will reload all the managers */ public void reload() { + configManager.reload(); + listenerManager.reload(); soundManager.reload(); messageManager.reload(); + actionManager.reload(); inventoryManager.reload(); getPluginManager().reload(); } + public BlobLibAPI getAPI() { + return api; + } + + public BlobLibUpdater getBloblibupdater() { + return bloblibupdater; + } + /** * Will retrieve the plugin manager. * This manager will handle all the plugins that are using BlobLib. @@ -145,6 +186,15 @@ public MessageManager getMessageManager() { return messageManager; } + /** + * Will retrieve the ActionManager + * + * @return The ActionManager + */ + public ActionManager getActionManager() { + return actionManager; + } + /** * Will retrieve the SoundManager * @@ -163,6 +213,15 @@ public VaultManager getVaultManager() { return vaultManager; } + /** + * Will retrieve the VaultManager + * + * @return The VaultManager + */ + public static VaultManager vaultManager() { + return getInstance().getVaultManager(); + } + /** * Will retrieve the HologramManager * @@ -234,4 +293,13 @@ public DropListenerManager getDropListenerManager() { public EngineHubManager getEngineHubManager() { return engineHubManager; } + + /** + * Will retrieve the DisguiseManager + * + * @return The DisguiseManager + */ + public DisguiseManager getDisguiseManager() { + return disguiseManager; + } } diff --git a/src/main/java/us/mytheria/bloblib/BlobLibAPI.java b/src/main/java/us/mytheria/bloblib/BlobLibAPI.java index a383082a..17f990f8 100644 --- a/src/main/java/us/mytheria/bloblib/BlobLibAPI.java +++ b/src/main/java/us/mytheria/bloblib/BlobLibAPI.java @@ -4,276 +4,212 @@ import org.bukkit.block.Block; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; +import us.mytheria.bloblib.api.*; +import us.mytheria.bloblib.disguises.Disguiser; +import us.mytheria.bloblib.entities.BlobEditor; import us.mytheria.bloblib.entities.inventory.VariableSelector; -import us.mytheria.bloblib.entities.listeners.BlobChatListener; -import us.mytheria.bloblib.entities.listeners.BlobDropListener; -import us.mytheria.bloblib.entities.listeners.BlobSelPosListener; -import us.mytheria.bloblib.entities.listeners.BlobSelectorListener; +import us.mytheria.bloblib.vault.multieconomy.ElasticEconomy; import java.util.List; import java.util.function.Consumer; /** * @author anjoismysign - * This class provides static methods to interact with the BlobLib API. - * It's not meant to change unless in a future in a big rewrite of the API. + * This class provides methods to interact with the BlobLib API. + * It's not meant to change now that it follows the singleton pattern. */ public class BlobLibAPI { - private static final BlobLib main = BlobLib.getInstance(); - - /** - * Adds a new chat listener - * - * @param player The player - * @param timeout The timeout - * @param consumer The consumer. The String is the message sent by the player - * @param timeoutMessageKey The timeout message key - * @param timerMessageKey The timer message key - */ + private static BlobLibAPI instance; + private final BlobLibListenerAPI listenerAPI; + private final BlobLibEconomyAPI economyAPI; + private final BlobLibHologramAPI hologramAPI; + private final BlobLibPermissionAPI permissionAPI; + private final BlobLibDisguiseAPI disguiseAPI; + private final BlobLibAssetAPI assetAPI; + + private BlobLibAPI(BlobLib plugin) { + this.listenerAPI = BlobLibListenerAPI.getInstance(plugin); + this.economyAPI = BlobLibEconomyAPI.getInstance(plugin); + this.hologramAPI = BlobLibHologramAPI.getInstance(plugin); + this.permissionAPI = BlobLibPermissionAPI.getInstance(plugin); + this.disguiseAPI = BlobLibDisguiseAPI.getInstance(plugin); + this.assetAPI = BlobLibAssetAPI.getInstance(plugin); + } + + public static BlobLibAPI getInstance(BlobLib plugin) { + if (instance == null) { + if (plugin == null) + throw new NullPointerException("injected dependency is null"); + BlobLibAPI.instance = new BlobLibAPI(plugin); + } + return instance; + } + + public static BlobLibAPI getInstance() { + return getInstance(null); + } + + public BlobLibListenerAPI getListenerAPI() { + return listenerAPI; + } + + public BlobLibEconomyAPI getEconomyAPI() { + return economyAPI; + } + + public BlobLibHologramAPI getHologramAPI() { + return hologramAPI; + } + + public BlobLibPermissionAPI getPermissionAPI() { + return permissionAPI; + } + + public BlobLibDisguiseAPI getDisguiseAPI() { + return disguiseAPI; + } + + public BlobLibAssetAPI getAssetAPI() { + return assetAPI; + } + + + @Deprecated public static void addChatListener(Player player, long timeout, Consumer consumer, String timeoutMessageKey, String timerMessageKey) { - BlobLib.getInstance().getChatManager().addChatListener(player, - BlobChatListener.smart(player, timeout, consumer, timeoutMessageKey, timerMessageKey)); + BlobLibListenerAPI.getInstance().addChatListener(player, timeout, consumer, timeoutMessageKey, timerMessageKey); } - /** - * Adds a new drop listener - * - * @param player The player - * @param consumer The consumer. The ItemStack is the item dropped. - * @param timerMessageKey The timer message key - */ + + @Deprecated public static void addDropListener(Player player, Consumer consumer, String timerMessageKey) { - BlobLib.getInstance().getDropListenerManager().addDropListener(player, - BlobDropListener.smart(player, consumer, timerMessageKey)); - } - - /** - * Adds a new position listener - * - * @param player The player - * @param timeout The timeout - * @param consumer The consumer. The Block is the block clicked. - * @param timeoutMessageKey The timeout message key - * @param timerMessageKey The timer message key - */ + BlobLibListenerAPI.getInstance().addDropListener(player, consumer, timerMessageKey); + } + + @Deprecated public static void addPositionListener(Player player, long timeout, Consumer consumer, String timeoutMessageKey, String timerMessageKey) { - BlobLib.getInstance().getPositionManager().addPositionListener(player, - BlobSelPosListener.smart(player, timeout, consumer, timeoutMessageKey, timerMessageKey)); - } - - /** - * Adds a new selector listener - * - * @param player The player - * @param consumer The consumer. The argument is the item selected. - * @param timerMessageKey The timer message key - * @param selector The selector - * @param The type of the selector - */ + BlobLibListenerAPI.getInstance().addPositionListener(player, timeout, consumer, timeoutMessageKey, timerMessageKey); + } + + @Deprecated public static void addSelectorListener(Player player, Consumer consumer, - String timerMessageKey, + @Nullable String timerMessageKey, VariableSelector selector) { - BlobLib.getInstance().getSelectorManager().addSelectorListener(player, - BlobSelectorListener.wise(player, consumer, timerMessageKey, selector)); + BlobLibListenerAPI.getInstance().addSelectorListener(player, consumer, timerMessageKey, selector); + } + + @Deprecated + public static void addEditorListener(Player player, Consumer consumer, + String timerMessageKey, + BlobEditor editor) { + BlobLibListenerAPI.getInstance().addEditorListener(player, consumer, timerMessageKey, editor); + } + + @Deprecated + public static ElasticEconomy getElasticEconomy() { + return BlobLibEconomyAPI.getInstance().getElasticEconomy(); + } + + @Deprecated + public static String format(double amount) { + return BlobLibEconomyAPI.getInstance().format(amount); } - /** - * Deposit an amount to a player - DO NOT USE NEGATIVE AMOUNTS - * - * @param player to deposit to - * @param amount amount to deposit - */ + @Deprecated public static void addCash(Player player, double amount) { - main.getVaultManager().getVaultEconomyWorker().addCash(player, amount); + BlobLibEconomyAPI.getInstance().addCash(player, amount); } - /** - * Withdraws an amount to a player - DO NOT USE NEGATIVE AMOUNTS - * - * @param player to deposit to - * @param amount amount to deposit - */ + @Deprecated public static void withdrawCash(Player player, double amount) { - main.getVaultManager().getVaultEconomyWorker().withdrawCash(player, amount); + BlobLibEconomyAPI.getInstance().withdrawCash(player, amount); } - /** - * Accepts negative values. - * Be sure that vault implementation supports negative values. - * - * @param player to deposit to - * @param amount amount to deposit - */ + @Deprecated public static void setCash(Player player, double amount) { - main.getVaultManager().getVaultEconomyWorker().setCash(player, amount); + BlobLibEconomyAPI.getInstance().setCash(player, amount); } - /** - * Checks if a player has a certain amount of money - * - * @param player to check - * @param amount amount to check - * @return true if player has amount - */ + @Deprecated public static boolean hasCashAmount(Player player, double amount) { - return main.getVaultManager().getVaultEconomyWorker().hasCashAmount(player, amount); + return BlobLibEconomyAPI.getInstance().hasCashAmount(player, amount); } - /** - * Gets the amount of money a player has - * - * @param player to check - * @return amount of money - */ + @Deprecated public static double getCash(Player player) { - return main.getVaultManager().getVaultEconomyWorker().getCash(player); + return BlobLibEconomyAPI.getInstance().getCash(player); } - /** - * @return true if a vault economy plugin is being used - */ + @Deprecated public static boolean hasVaultEconomyDependency() { - return main.getVaultManager().isVaultEcoInstalled(); + return BlobLibEconomyAPI.getInstance().hasVaultEconomyProvider(); } - /** - * @return true if a vault permission plugin is being used - */ + @Deprecated public static boolean hasVaultPermissionsDependency() { - return main.getVaultManager().isVaultPermsInstalled(); + return BlobLibEconomyAPI.getInstance().hasVaultPermissionsProvider(); } - /** - * Creates a hologram - * - * @param name name of hologram - * @param location Bukkit's Location of hologram - * @param lines lines of hologram - */ + @Deprecated public static void createHologram(String name, Location location, List lines) { - main.getHologramManager().create(name, location, lines); + BlobLibHologramAPI.getInstance().createHologram(name, location, lines); } - /** - * Creates a hologram - * - * @param name name of hologram - * @param location Bukkit's Location of hologram - * @param lines lines of hologram - * @param saveToConfig if true, hologram will be saved in configuration - */ + @Deprecated public static void createHologram(String name, Location location, List lines, boolean saveToConfig) { - main.getHologramManager().create(name, location, lines, saveToConfig); + BlobLibHologramAPI.getInstance().createHologram(name, location, lines, saveToConfig); } - /** - * Updates a hologram - * - * @param name name of hologram - */ + @Deprecated public static void updateHologram(String name) { - main.getHologramManager().update(name); + BlobLibHologramAPI.getInstance().updateHologram(name); } - /** - * Deletes a hologram - * - * @param name name of hologram - */ + @Deprecated public static void removeHologram(String name) { - main.getHologramManager().remove(name); + BlobLibHologramAPI.getInstance().removeHologram(name); } - /** - * Sets a hologram's lines - * - * @param name name of hologram - * @param lines lines of hologram - */ + @Deprecated public static void setHologramLines(String name, List lines) { - main.getHologramManager().setLines(name, lines); - } - - /** - * Add permission to a player ONLY for the world the player is currently on. - * This is a world-specific operation, if you want to add global permission - * you must explicitly use NULL for the world. - * - * @param player The player to add the permission to - * @param permission The permission to add - * @return true if the permission was added, false if the player already had the permission - */ + BlobLibHologramAPI.getInstance().setHologramLines(name, lines); + } + + @Deprecated public static boolean addPermission(Player player, String permission) { - return main.getVaultManager().getVaultPermissionsWorker().addPermission(player, permission); - } - - /** - * Remove permission from a player. - * Will attempt to remove permission from the player on the player's - * current world. This is NOT a global operation. - * - * @param player The player to remove the permission from - * @param permission The permission to remove - * @return true if the permission was removed, false if the player did not have the permission - */ + return BlobLibPermissionAPI.getInstance().addPermission(player, permission); + } + + @Deprecated public static boolean removePermission(Player player, String permission) { - return main.getVaultManager().getVaultPermissionsWorker().removePermission(player, permission); - } - - /** - * Add permission to a player. Supports NULL value for World if the permission - * system registered supports global permissions. But May return odd values if - * the servers registered permission system does not have a global permission store. - * - * @param player The player to add the permission to - * @param permission The permission to add - * @param world The world to add the permission to (null for global) - * @return true if the permission was added, false if the player already had the permission - */ + return BlobLibPermissionAPI.getInstance().removePermission(player, permission); + } + + @Deprecated public static boolean addPermission(Player player, String permission, String world) { - return main.getVaultManager().getVaultPermissionsWorker().addPermission(player, permission, world); - } - - /** - * Remove permission from a player. Supports NULL value for World if the - * permission system registered supports global permissions. But May return - * odd values if the servers registered permission system does not have a - * global permission store. - * - * @param player The player to remove the permission from - * @param permission The permission to remove - * @param world The world to remove the permission from - * (null for global) - * @return true if the permission was removed, false if the player did not have the permission - */ + return BlobLibPermissionAPI.getInstance().addPermission(player, permission, world); + } + + @Deprecated public static boolean removePermission(Player player, String permission, String world) { - return main.getVaultManager().getVaultPermissionsWorker().removePermission(player, permission, world); + return BlobLibPermissionAPI.getInstance().removePermission(player, permission, world); } - /** - * Add permission to a player. Will attempt to add permission - * to the player on the player, GLOBALLY. - * - * @param player The player to add the permission to - * @param permission The permission to add - * @return true if the permission was added, false if the player already had the permission - */ + @Deprecated public static boolean addGlobalPermission(Player player, String permission) { - return main.getVaultManager().getVaultPermissionsWorker().addGlobalPermission(player, permission); + return BlobLibPermissionAPI.getInstance().addGlobalPermission(player, permission); } - /** - * Remove permission from a player. Will attempt to remove permission - * from the player on the player, GLOBALLY. - * - * @param player The player to remove the permission from - * @param permission The permission to remove - * @return true if the permission was removed, false if the player did not have the permission - */ + @Deprecated public static boolean removeGlobalPermission(Player player, String permission) { - return main.getVaultManager().getVaultPermissionsWorker().removeGlobalPermission(player, permission); + return BlobLibPermissionAPI.getInstance().removeGlobalPermission(player, permission); + } + + @Deprecated + public static Disguiser getDisguiser() { + return BlobLibDisguiseAPI.getInstance().getDisguiser(); } } diff --git a/src/main/java/us/mytheria/bloblib/BlobLibAssetAPI.java b/src/main/java/us/mytheria/bloblib/BlobLibAssetAPI.java index 3c0559c6..81197405 100644 --- a/src/main/java/us/mytheria/bloblib/BlobLibAssetAPI.java +++ b/src/main/java/us/mytheria/bloblib/BlobLibAssetAPI.java @@ -1,138 +1,189 @@ package us.mytheria.bloblib; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import us.mytheria.bloblib.entities.inventory.BlobInventory; +import org.jetbrains.annotations.Nullable; +import us.mytheria.bloblib.action.Action; +import us.mytheria.bloblib.api.BlobLibActionAPI; +import us.mytheria.bloblib.api.BlobLibInventoryAPI; +import us.mytheria.bloblib.api.BlobLibMessageAPI; +import us.mytheria.bloblib.api.BlobLibSoundAPI; +import us.mytheria.bloblib.entities.inventory.*; import us.mytheria.bloblib.entities.message.BlobSound; import us.mytheria.bloblib.entities.message.ReferenceBlobMessage; -import us.mytheria.bloblib.managers.InventoryManager; -import us.mytheria.bloblib.managers.MessageManager; -import us.mytheria.bloblib.managers.SoundManager; +import us.mytheria.bloblib.managers.*; import java.io.File; +import java.util.Optional; +/** + * Please use the API classes instead of this class. + */ public class BlobLibAssetAPI { - private static final BlobLib main = BlobLib.getInstance(); + private static BlobLibAssetAPI instance; + private final BlobLibSoundAPI soundAPI; + private final BlobLibInventoryAPI inventoryAPI; + private final BlobLibActionAPI actionAPI; + private final BlobLibMessageAPI messageAPI; + + private BlobLibAssetAPI(BlobLib plugin) { + this.soundAPI = BlobLibSoundAPI.getInstance(plugin); + this.inventoryAPI = BlobLibInventoryAPI.getInstance(plugin); + this.actionAPI = BlobLibActionAPI.getInstance(plugin); + this.messageAPI = BlobLibMessageAPI.getInstance(plugin); + } - /** - * @return The inventory manager - */ + public static BlobLibAssetAPI getInstance(BlobLib plugin) { + if (instance == null) { + if (plugin == null) + throw new NullPointerException("injected dependency is null"); + BlobLibAssetAPI.instance = new BlobLibAssetAPI(plugin); + } + return instance; + } + + public static BlobLibAssetAPI getInstance() { + return getInstance(null); + } + + public BlobLibSoundAPI getSoundAPI() { + return soundAPI; + } + + public BlobLibInventoryAPI getInventoryAPI() { + return inventoryAPI; + } + + public BlobLibActionAPI getActionAPI() { + return actionAPI; + } + + public BlobLibMessageAPI getMessageAPI() { + return messageAPI; + } + + @Deprecated public static InventoryManager getInventoryManager() { - return main.getInventoryManager(); + return BlobLibInventoryAPI.getInstance().getInventoryManager(); } - /** - * @return The message manager - */ + @Deprecated public static MessageManager getMessageManager() { - return main.getMessageManager(); + return BlobLibMessageAPI.getInstance().getMessageManager(); + } + + @Deprecated + public static ActionManager getActionManager() { + return BlobLibActionAPI.getInstance().getActionManager(); } - /** - * @return The sound manager - */ + @Deprecated public static SoundManager getSoundManager() { - return main.getSoundManager(); + return BlobLibSoundAPI.getInstance().getSoundManager(); } - /** - * @param key Key that points to the inventory - * @return The inventory - */ + @Deprecated + public static InventoryBuilderCarrier getInventoryBuilderCarrier(String key) { + return getInventoryManager().getInventoryBuilderCarrier(key); + } + + @Deprecated public static BlobInventory getBlobInventory(String key) { return getInventoryManager().getInventory(key); } - /** - * @param key The key of the message - * @return The message - */ + @Deprecated + public static InventoryBuilderCarrier getMetaInventoryBuilderCarrier(String key) { + return getInventoryManager().getMetaInventoryBuilderCarrier(key); + } + + @Deprecated + public static MetaBlobInventory getMetaBlobInventory(String key) { + return getInventoryManager().getMetaInventory(key); + } + + @Deprecated + public static Optional hasMetaInventoryShard(String type) { + return Optional.ofNullable(getInventoryManager().getMetaInventoryShard(type)); + } + + @Deprecated public static ReferenceBlobMessage getMessage(String key) { return getMessageManager().getMessage(key); } - /** - * @param key The key of the message - * @param player The player to send the message to - */ + @Deprecated + public static ReferenceBlobMessage getMessage(String key, String locale) { + return getMessageManager().getMessage(key, locale); + } + + @Deprecated + public static ReferenceBlobMessage getLocaleMessageOrDefault(String key, String locale) { + ReferenceBlobMessage localeMessage = getMessageManager().getMessage(key, locale); + if (localeMessage != null) + return localeMessage; + return getMessageManager().getMessage(key); + } + + @Deprecated + public static ReferenceBlobMessage getLocaleMessageOrDefault(String key, Player player) { + String locale = player.getLocale(); + return getLocaleMessageOrDefault(key, locale); + } + + @Deprecated + @Nullable + public static Action getAction(String key) { + return getActionManager().getAction(key); + } + + @Deprecated public static void sendMessage(String key, Player player) { getMessageManager().send(player, key); } - /** - * @param key The key of the sound - * @return The sound - */ + @Deprecated public static BlobSound getSound(String key) { return getSoundManager().getSound(key); } - /** - * @param key The key of the sound - * @param player The player to play the sound - */ + @Deprecated public static void playSound(String key, Player player) { getSoundManager().play(player, key); } - /** - * @return The messages file - */ - @NotNull + @Deprecated public static File getMessagesDirectory() { - return main.getFileManager().messagesDirectory(); + return BlobLibMessageAPI.getInstance().getMessagesDirectory(); } - /** - * @return The messages file path - */ - @NotNull + @Deprecated public static String getMessagesFilePath() { - return main.getFileManager().messagesDirectory().getPath(); + return BlobLibMessageAPI.getInstance().getMessagesFilePath(); } - /** - * @return The sounds file - */ - @NotNull + @Deprecated public static File getSoundsDirectory() { - return main.getFileManager().soundsDirectory(); + return BlobLibSoundAPI.getInstance().getSoundsDirectory(); } - /** - * @return The sounds file path - */ - @NotNull + @Deprecated public static String getSoundsFilePath() { - return main.getFileManager().soundsDirectory().getPath(); + return BlobLibSoundAPI.getInstance().getSoundsFilePath(); } - /** - * Retrieves a file from the inventories' directory. - * - * @return The inventories file - */ - @NotNull + @Deprecated public static File getInventoriesDirectory() { - return main.getFileManager().inventoriesDirectory(); + return BlobLibInventoryAPI.getInstance().getInventoriesDirectory(); } - /** - * @return The inventories file path - */ - @NotNull + @Deprecated public static String getInventoriesFilePath() { - return main.getFileManager().inventoriesDirectory().getPath(); + return BlobLibInventoryAPI.getInstance().getInventoriesFilePath(); } - /** - * Attempts to build an inventory from the given file name. - * If the inventory is not found, a NullPointerException is thrown. - * - * @param fileName The file name - * @return The inventory - */ + @Deprecated public static BlobInventory buildInventory(String fileName) { BlobInventory inventory = BlobLibAssetAPI.getInventoryManager().cloneInventory(fileName); if (inventory == null) { diff --git a/src/main/java/us/mytheria/bloblib/BlobLibBungee.java b/src/main/java/us/mytheria/bloblib/BlobLibBungee.java deleted file mode 100644 index 6c708273..00000000 --- a/src/main/java/us/mytheria/bloblib/BlobLibBungee.java +++ /dev/null @@ -1,55 +0,0 @@ -package us.mytheria.bloblib; - -import net.md_5.bungee.api.config.ServerInfo; -import net.md_5.bungee.api.plugin.Plugin; -import us.mytheria.bloblib.bungee.PluginMessageEventListener; -import us.mytheria.bloblib.entities.message.BungeeMessage; - -/** - * BlobLib is planned to be a Bungeecord plugin as well. - */ -public class BlobLibBungee extends Plugin { - private static BlobLibBungee instance; - - /** - * Will retrieve the instance of the plugin - * - * @return the instance of the plugin - */ - public static BlobLibBungee getInstance() { - return instance; - } - - /** - * Called when the plugin is enabled - */ - @Override - public void onEnable() { - instance = this; - this.getProxy().registerChannel("BlobLib"); - new PluginMessageEventListener(); - } - - /** - * Called when the plugin is disabled - */ - @Override - public void onDisable() { - } - - /** - * Send data by any available means to this server. - * - * @param server the server to send this data via - * @param message the message to send - * @param queue hold the message for later sending if it cannot be sent - * immediately. - * @return true if the message was sent immediately, - * false otherwise if queue is true, it has been queued, if it - * is false it has been discarded. - */ - - public static boolean sendMessage(BungeeMessage message, ServerInfo server, boolean queue) { - return server.sendData("BlobLib", BungeeMessage.serialize(message), queue); - } -} diff --git a/src/main/java/us/mytheria/bloblib/BlobLibDevAPI.java b/src/main/java/us/mytheria/bloblib/BlobLibDevAPI.java index 261c38d9..7cc8a2b4 100644 --- a/src/main/java/us/mytheria/bloblib/BlobLibDevAPI.java +++ b/src/main/java/us/mytheria/bloblib/BlobLibDevAPI.java @@ -1,50 +1,48 @@ package us.mytheria.bloblib; import org.jetbrains.annotations.NotNull; +import us.mytheria.bloblib.api.BlobLibInventoryAPI; +import us.mytheria.bloblib.api.BlobLibMessageAPI; +import us.mytheria.bloblib.api.BlobLibSoundAPI; import java.io.File; /** * @author anjoismysign * It's meant to hold quick/static methods that later are meant to be - * moved to a different class, such as BlobLibAPI. + * moved to a different, singleton pattern class. * Consider all methods as deprecated and subject to change. */ +@Deprecated public class BlobLibDevAPI { - private static final BlobLib main = BlobLib.getInstance(); /** - * @return The messages file - * @deprecated Use {@link BlobLibAssetAPI#getMessagesDirectory()} instead + * @deprecated Use {@link BlobLibMessageAPI#getMessagesDirectory()} instead * to avoid confusion. */ @Deprecated @NotNull public static File getMessagesFile() { - return main.getFileManager().messagesDirectory(); + throw new UnsupportedOperationException("Use BlobLibMessageAPI instead"); } /** - * @return The sounds file - * @deprecated Use {@link BlobLibAssetAPI#getSoundsDirectory()} instead + * @deprecated Use {@link BlobLibSoundAPI#getSoundsDirectory()} instead * to avoid confusion. */ @Deprecated @NotNull public static File getSoundsFile() { - return main.getFileManager().soundsDirectory(); + throw new UnsupportedOperationException("Use BlobLibSoundAPI instead"); } /** - * Retrieves a file from the inventories' directory. - * - * @return The inventories file - * @deprecated Use {@link BlobLibAssetAPI#getInventoriesDirectory()} instead + * @deprecated Use {@link BlobLibInventoryAPI#getInventoriesDirectory()} instead * to avoid confusion. */ @Deprecated @NotNull public static File getInventoriesFile() { - return main.getFileManager().inventoriesDirectory(); + throw new UnsupportedOperationException("Use BlobLibInventoryAPI instead"); } } diff --git a/src/main/java/us/mytheria/bloblib/BlobLibUpdater.java b/src/main/java/us/mytheria/bloblib/BlobLibUpdater.java new file mode 100644 index 00000000..5e83251c --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/BlobLibUpdater.java @@ -0,0 +1,181 @@ +package us.mytheria.bloblib; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.plugin.java.JavaPlugin; +import us.mytheria.bloblib.api.BlobLibMessageAPI; +import us.mytheria.bloblib.entities.PluginUpdater; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.ProtocolException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; + +public class BlobLibUpdater implements PluginUpdater { + private final BlobLib plugin; + private final String currentVersion; + private boolean updateAvailable; + private final UpdaterListener listener; + private String latestVersion; + + protected BlobLibUpdater(BlobLib plugin) { + this.plugin = plugin; + this.currentVersion = plugin.getDescription().getVersion(); + this.listener = new UpdaterListener(plugin, this); + reload(); + } + + /** + * Will reload the updater and re-run their checks + */ + public void reload() { + getLatestUrl(); + updateAvailable = !isLatestVersion(); + listener.reload(updateAvailable); + } + + /** + * Will return true if there is an update available + * + * @return true if there is an update available + */ + public boolean hasAvailableUpdate() { + return updateAvailable; + } + + public String getLatestVersion() { + return latestVersion; + } + + private boolean isLatestVersion() { + return currentVersion.equals(latestVersion); + } + + /** + * Will attempt to download latest version of BlobLib + * + * @return true if the download was successful + */ + public boolean download() { + if (!updateAvailable) + return false; + URL url; + try { + url = new URL(getLatestUrl()); + } catch (MalformedURLException e) { + BlobLib.getAnjoLogger().error("Could not download latest version of BlobLib because " + + "the URL was malformed"); + return false; + } + Path existentPath = Path.of("plugins", "BlobLib-" + currentVersion + ".jar"); + try { + Files.deleteIfExists(existentPath); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + Path targetPath = Path.of("plugins", "BlobLib-" + latestVersion + ".jar"); + try (InputStream inputStream = url.openStream()) { + Files.copy(inputStream, targetPath); + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + public JavaPlugin getPlugin() { + return plugin; + } + + private String getLatestUrl() { + String repoUrl = "https://api.github.com/repos/anjoismysign/BlobLib/releases"; + URL url; + try { + url = new URL(repoUrl); + } catch (MalformedURLException e) { + e.printStackTrace(); + return null; + } + HttpURLConnection connection; + try { + connection = (HttpURLConnection) url.openConnection(); + } catch (IOException e) { + plugin.getLogger().severe("Could not connect to GitHub to check for updates"); + return null; + } + try { + connection.setRequestMethod("GET"); + } catch (ProtocolException e) { + e.printStackTrace(); + return null; + } + BufferedReader reader; + try { + reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + StringBuilder response = new StringBuilder(); + String line; + try { + while ((line = reader.readLine()) != null) { + response.append(line); + } + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + + Gson gson = new Gson(); + JsonArray releases = gson.fromJson(response.toString(), JsonArray.class); + JsonObject latestRelease = releases.get(0).getAsJsonObject(); + latestVersion = latestRelease.get("tag_name").getAsString(); + if (latestVersion.startsWith("v")) + latestVersion = latestVersion.substring(1); + return latestRelease.get("assets").getAsJsonArray().get(0).getAsJsonObject().get("browser_download_url").getAsString(); + } + + private record UpdaterListener(BlobLib plugin, PluginUpdater updater) implements Listener { + + private void reload(boolean updateAvailable) { + HandlerList.unregisterAll(this); + if (updateAvailable) + Bukkit.getPluginManager().registerEvents(this, plugin); + } + + @EventHandler + public void handle(PlayerJoinEvent event) { + Player player = event.getPlayer(); + if (!player.hasPermission("bloblib.admin")) + return; + Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> { + if (player == null || !player.isOnline()) + return; + BlobLibMessageAPI.getInstance().getMessage("BlobLib.Updater-Available") + .modder() + .replace("%randomColor%", plugin.getColorManager().randomColor().toString()) + .replace("update %plugin%", "update") + .replace("%plugin%", plugin.getName()) + .replace("%version%", updater.getLatestVersion()) + .get() + .handle(player); + }, 30L); + } + } +} diff --git a/src/main/java/us/mytheria/bloblib/Experience.java b/src/main/java/us/mytheria/bloblib/Experience.java new file mode 100644 index 00000000..63e39b47 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/Experience.java @@ -0,0 +1,127 @@ +package us.mytheria.bloblib; + +import org.bukkit.entity.Player; + +/** + * A utility for managing player experience. + *

+ * Took from: + * Gist + * + * @author Jikoo + */ +public final class Experience { + + /** + * Calculate a player's total experience based on level and progress to next. + * + * @param player the Player + * @return the amount of experience the Player has + * @see Experience#Leveling_up + */ + public static int getExp(Player player) { + return getExpFromLevel(player.getLevel()) + + Math.round(getExpToNext(player.getLevel()) * player.getExp()); + } + + /** + * Calculate total experience based on level. + * + * @param level the level + * @return the total experience calculated + * @see Experience#Leveling_up + */ + public static int getExpFromLevel(int level) { + if (level > 30) { + return (int) (4.5 * level * level - 162.5 * level + 2220); + } + if (level > 15) { + return (int) (2.5 * level * level - 40.5 * level + 360); + } + return level * level + 6 * level; + } + + /** + * Calculate level (including progress to next level) based on total experience. + * + * @param exp the total experience + * @return the level calculated + */ + public static double getLevelFromExp(long exp) { + int level = getIntLevelFromExp(exp); + + // Get remaining exp progressing towards next level. Cast to float for next bit of math. + float remainder = exp - (float) getExpFromLevel(level); + + // Get level progress with float precision. + float progress = remainder / getExpToNext(level); + + // Slap both numbers together and call it a day. While it shouldn't be possible for progress + // to be an invalid value (value < 0 || 1 <= value) + return ((double) level) + progress; + } + + /** + * Calculate level based on total experience. + * + * @param exp the total experience + * @return the level calculated + */ + public static int getIntLevelFromExp(long exp) { + if (exp > 1395) { + return (int) ((Math.sqrt(72 * exp - 54215D) + 325) / 18); + } + if (exp > 315) { + return (int) (Math.sqrt(40 * exp - 7839D) / 10 + 8.1); + } + if (exp > 0) { + return (int) (Math.sqrt(exp + 9D) - 3); + } + return 0; + } + + /** + * Get the total amount of experience required to progress to the next level. + * + * @param level the current level + * @see Experience#Leveling_up + */ + private static int getExpToNext(int level) { + if (level >= 30) { + // Simplified formula. Internal: 112 + (level - 30) * 9 + return level * 9 - 158; + } + if (level >= 15) { + // Simplified formula. Internal: 37 + (level - 15) * 5 + return level * 5 - 38; + } + // Internal: 7 + level * 2 + return level * 2 + 7; + } + + /** + * Change a Player's experience. + * + *

This method is preferred over {@link Player#giveExp(int)}. + *
In older versions the method does not take differences in exp per level into account. + * This leads to overlevelling when granting players large amounts of experience. + *
In modern versions, while differing amounts of experience per level are accounted for, the + * approach used is loop-heavy and requires an excessive number of calculations, which makes it + * quite slow. + * + * @param player the Player affected + * @param exp the amount of experience to add or remove + */ + public static void changeExp(Player player, float exp) { + exp += getExp(player); + + if (exp < 0) { + exp = 0; + } + + double levelAndExp = getLevelFromExp((long) exp); + int level = (int) levelAndExp; + player.setLevel(level); + player.setExp((float) (levelAndExp - level)); + } +} \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/FuelAPI.java b/src/main/java/us/mytheria/bloblib/FuelAPI.java new file mode 100644 index 00000000..8fbd3103 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/FuelAPI.java @@ -0,0 +1,335 @@ +package us.mytheria.bloblib; + +import me.anjoismysign.anjo.entities.Result; +import org.bukkit.Material; +import us.mytheria.bloblib.entities.Fuel; +import us.mytheria.bloblib.itemstack.ItemStackBuilder; + +import java.util.HashMap; +import java.util.Map; + +/* + * Provides Fuel objects for vanilla items. + * Might want to check if item is custom. + * If so, might want to apply your own + * fuel time. + */ +public class FuelAPI { + private static FuelAPI instance; + + private FuelAPI() { + mapping.put("LAVA_BUCKET", + Fuel.withReplacement(ItemStackBuilder.build(Material.BUCKET).build(), + 20000)); + mapping.put("COAL_BLOCK", Fuel.of(16000)); + mapping.put("DRIED_KELP_BLOCK", Fuel.of(4000)); + mapping.put("BLAZE_ROD", Fuel.of(2400)); + mapping.put("COAL", Fuel.of(1600)); + mapping.put("CHARCOAL", Fuel.of(1600)); + for (String boatMaterial : new String[]{ + "OAK_BOAT", + "BIRCH_BOAT", + "SPRUCE_BOAT", + "JUNGLE_BOAT", + "ACACIA_BOAT", + "DARK_OAK_BOAT", + "MANGROVE_BOAT", + "OAK_CHEST_BOAT", + "BIRCH_CHEST_BOAT", + "SPRUCE_CHEST_BOAT", + "JUNGLE_CHEST_BOAT", + "ACACIA_CHEST_BOAT", + "DARK_OAK_CHEST_BOAT", + "MANGROVE_CHEST_BOAT", + "BAMBOO_RAFT", + "BAMBOO_CHEST_RAFT", + "CHERRY_BOAT", + "CHERRY_CHEST_BOAT" + }) { + mapping.put(boatMaterial, Fuel.of(1200)); + } + mapping.put(Material.SCAFFOLDING.toString(), Fuel.of(50)); + for (String hangingSign : new String[]{ + "ACACIA_HANGING_SIGN", + "BAMBOO_HANGING_SIGN", + "BIRCH_HANGING_SIGN", + "DARK_OAK_HANGING_SIGN", + "JUNGLE_HANGING_SIGN", + "OAK_HANGING_SIGN", + "SPRUCE_HANGING_SIGN", + "MANGROVE_HANGING_SIGN", + "CHERRY_HANGING_SIGN"}) { + mapping.put(hangingSign.toString(), Fuel.of(200)); + } + mapping.put(Material.BAMBOO_MOSAIC.toString(), Fuel.of(300)); + mapping.put("BEE_NEST", Fuel.of(300)); + mapping.put("BEEHIVE", Fuel.of(300)); + mapping.put("CHISELED_BOOKSHELF", Fuel.of(300)); + for (String woodMaterial : new String[]{ + "OAK_PLANKS", + "BIRCH_PLANKS", + "SPRUCE_PLANKS", + "JUNGLE_PLANKS", + "ACACIA_PLANKS", + "DARK_OAK_PLANKS", + "OAK_LOG", + "BIRCH_LOG", + "SPRUCE_LOG", + "JUNGLE_LOG", + "ACACIA_LOG", + "DARK_OAK_LOG", + "STRIPPED_OAK_LOG", + "STRIPPED_BIRCH_LOG", + "STRIPPED_SPRUCE_LOG", + "STRIPPED_JUNGLE_LOG", + "STRIPPED_ACACIA_LOG", + "STRIPPED_DARK_OAK_LOG", + "OAK_WOOD", + "BIRCH_WOOD", + "SPRUCE_WOOD", + "JUNGLE_WOOD", + "ACACIA_WOOD", + "DARK_OAK_WOOD", + "STRIPPED_OAK_WOOD", + "STRIPPED_BIRCH_WOOD", + "STRIPPED_SPRUCE_WOOD", + "STRIPPED_JUNGLE_WOOD", + "STRIPPED_ACACIA_WOOD", + "STRIPPED_DARK_OAK_WOOD", + "OAK_STAIRS", + "BIRCH_STAIRS", + "SPRUCE_STAIRS", + "JUNGLE_STAIRS", + "ACACIA_STAIRS", + "DARK_OAK_STAIRS", + "OAK_PRESSURE_PLATE", + "BIRCH_PRESSURE_PLATE", + "SPRUCE_PRESSURE_PLATE", + "JUNGLE_PRESSURE_PLATE", + "ACACIA_PRESSURE_PLATE", + "DARK_OAK_PRESSURE_PLATE", + "OAK_TRAPDOOR", + "BIRCH_TRAPDOOR", + "SPRUCE_TRAPDOOR", + "JUNGLE_TRAPDOOR", + "ACACIA_TRAPDOOR", + "DARK_OAK_TRAPDOOR", + "ACACIA_FENCE_GATE", + "BIRCH_FENCE_GATE", + "SPRUCE_FENCE_GATE", + "JUNGLE_FENCE_GATE", + "ACACIA_FENCE_GATE", + "DARK_OAK_FENCE_GATE", + "ACACIA_FENCE", + "BIRCH_FENCE", + "SPRUCE_FENCE", + "JUNGLE_FENCE", + "ACACIA_FENCE", + "DARK_OAK_FENCE", + "MANGROVE_PLANKS", + "MANGROVE_LOG", + "STRIPPED_MANGROVE_LOG", + "MANGROVE_WOOD", + "STRIPPED_MANGROVE_WOOD", + "MANGROVE_STAIRS", + "MANGROVE_PRESSURE_PLATE", + "MANGROVE_TRAPDOOR", + "MANGROVE_FENCE_GATE", + "MANGROVE_FENCE", + "BAMBOO_BLOCK", + "STRIPPED_BAMBOO_BLOCK", + "BAMBOO_STAIRS", + "BAMBOO_PRESSURE_PLATE", + "BAMBOO_TRAPDOOR", + "BAMBOO_FENCE_GATE", + "BAMBOO_FENCE", + "CHERRY_PLANKS", + "CHERRY_LOG", + "STRIPPED_CHERRY_LOG", + "CHERRY_WOOD", + "STRIPPED_CHERRY_WOOD", + "CHERRY_STAIRS", + "CHERRY_PRESSURE_PLATE", + "CHERRY_TRAPDOOR", + "CHERRY_FENCE_GATE", + "CHERRY_FENCE" + }) { + mapping.put(woodMaterial, Fuel.of(300)); + } + mapping.put("MANGROVE_ROOTS", Fuel.of(300)); + mapping.put("LADDER", Fuel.of(300)); + mapping.put("CRAFTING_TABLE", Fuel.of(300)); + mapping.put("CARTOGRAPHY_TABLE", Fuel.of(300)); + mapping.put("FLETCHING_TABLE", Fuel.of(300)); + mapping.put("SMITHING_TABLE", Fuel.of(300)); + mapping.put("LOOM", Fuel.of(300)); + mapping.put("BOOKSHELF", Fuel.of(300)); + mapping.put("LECTERN", Fuel.of(300)); + mapping.put("COMPOSTER", Fuel.of(300)); + mapping.put("CHEST", Fuel.of(300)); + mapping.put("TRAPPED_CHEST", Fuel.of(300)); + mapping.put("BARREL", Fuel.of(300)); + mapping.put("DAYLIGHT_DETECTOR", Fuel.of(300)); + mapping.put("JUKEBOX", Fuel.of(300)); + mapping.put("NOTE_BLOCK", Fuel.of(300)); + mapping.put("FISHING_ROD", Fuel.of(300)); + for (String bannerMaterial : new String[]{ + "WHITE_BANNER", + "ORANGE_BANNER", + "MAGENTA_BANNER", + "LIGHT_BLUE_BANNER", + "YELLOW_BANNER", + "LIME_BANNER", + "PINK_BANNER", + "GRAY_BANNER", + "LIGHT_GRAY_BANNER", + "CYAN_BANNER", + "PURPLE_BANNER", + "BLUE_BANNER", + "BROWN_BANNER", + "GREEN_BANNER", + "RED_BANNER", + "BLACK_BANNER", + }) { + mapping.put(bannerMaterial, Fuel.of(300)); + } + for (String doorMaterial : new String[]{ + "OAK_DOOR", + "BIRCH_DOOR", + "SPRUCE_DOOR", + "JUNGLE_DOOR", + "ACACIA_DOOR", + "DARK_OAK_DOOR", + }) { + mapping.put(doorMaterial, Fuel.of(200)); + } + + for (String sign : new String[]{ + "OAK_SIGN", + "BIRCH_SIGN", + "SPRUCE_SIGN", + "JUNGLE_SIGN", + "ACACIA_SIGN", + "DARK_OAK_SIGN", + "MANGROVE_SIGN", + "BAMBOO_SIGN.toString()", + "CHERRY_SIGN" + }) { + mapping.put(sign, Fuel.of(200)); + } + for (String tool : new String[]{ + "WOODEN_PICKAXE", + "WOODEN_SHOVEL", + "WOODEN_HOE", + "WOODEN_AXE", + "WOODEN_SWORD" + }) { + mapping.put(tool, Fuel.of(200)); + } + for (String slab : new String[]{ + "OAK_SLAB", + "BIRCH_SLAB", + "SPRUCE_SLAB", + "JUNGLE_SLAB", + "ACACIA_SLAB", + "DARK_OAK_SLAB", + "MANGROVE_SLAB", + "BAMBOO_SLAB", + "CHERRY_SLAB" + }) { + mapping.put(slab, Fuel.of(150)); + } + for (String button : new String[]{ + "OAK_BUTTON", + "BIRCH_BUTTON", + "SPRUCE_BUTTON", + "JUNGLE_BUTTON", + "ACACIA_BUTTON", + "DARK_OAK_BUTTON", + "MANGROVE_BUTTON", + "BAMBOO_BUTTON", + "CHERRY_BUTTON" + }) { + mapping.put(button, Fuel.of(100)); + } + for (String saplingMaterial : new String[]{ + "OAK_SAPLING", + "BIRCH_SAPLING", + "SPRUCE_SAPLING", + "JUNGLE_SAPLING", + "ACACIA_SAPLING", + "DARK_OAK_SAPLING", + "MANGROVE_PROPAGULE", + "BAMBOO_SAPLING", + "CHERRY_SAPLING" + }) { + mapping.put(saplingMaterial, Fuel.of(100)); + } + for (String woolMaterial : new String[]{ + "WHITE_WOOL", + "ORANGE_WOOL", + "MAGENTA_WOOL", + "LIGHT_BLUE_WOOL", + "YELLOW_WOOL", + "LIME_WOOL", + "PINK_WOOL", + "GRAY_WOOL", + "LIGHT_GRAY_WOOL", + "CYAN_WOOL", + "PURPLE_WOOL", + "BLUE_WOOL", + "BROWN_WOOL", + "GREEN_WOOL", + "RED_WOOL", + "BLACK_WOOL", + }) { + mapping.put(woolMaterial, Fuel.of(100)); + } + for (String carpetMaterial : new String[]{ + "WHITE_CARPET", + "ORANGE_CARPET", + "MAGENTA_CARPET", + "LIGHT_BLUE_CARPET", + "YELLOW_CARPET", + "LIME_CARPET", + "PINK_CARPET", + "GRAY_CARPET", + "LIGHT_GRAY_CARPET", + "CYAN_CARPET", + "PURPLE_CARPET", + "BLUE_CARPET", + "BROWN_CARPET", + "GREEN_CARPET", + "RED_CARPET", + "BLACK_CARPET", + }) { + mapping.put(carpetMaterial, Fuel.of(67)); + } + mapping.put("BAMBOO", Fuel.of(50)); + } + + public static FuelAPI getInstance() { + if (instance == null) { + FuelAPI.instance = new FuelAPI(); + } + return instance; + } + + + private final Map mapping = new HashMap<>(); + + /** + * Checks if the material is a fuel, whether + * it belongs to a custom item or not. + * NATIVE AND TESTED FOR 1.19.4! + * + * @param material The material to check + * @return A Result containing the Fuel if it is a fuel, an invalid Result otherwise. + */ + public Result isFuel(Material material) { + Fuel fuel = mapping.get(material.toString()); + if (fuel == null) + return Result.invalidBecauseNull(); + return Result.ofNullable(fuel); + } +} \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/action/Action.java b/src/main/java/us/mytheria/bloblib/action/Action.java index 71ae0f08..3d2403fc 100644 --- a/src/main/java/us/mytheria/bloblib/action/Action.java +++ b/src/main/java/us/mytheria/bloblib/action/Action.java @@ -3,8 +3,8 @@ import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Entity; -import javax.annotation.Nullable; import java.util.Objects; +import java.util.function.Function; /** * An action that can be performed on an entity @@ -20,21 +20,26 @@ public abstract class Action { */ public static Action fromConfigurationSection(ConfigurationSection section) { String type = Objects.requireNonNull(section.getString("Type"), "Action.Type is null"); - switch (type) { - case "ActorCommand", "ConsoleCommand" -> { + ActionType actionType; + try { + actionType = ActionType.valueOf(type); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Unknown Action Type: " + type); + } + switch (actionType) { + case ACTOR_COMMAND -> { String command = Objects.requireNonNull(section.getString("Command"), "Action.Command is null"); - if (type.equals("ConsoleCommand")) - return ConsoleCommandAction.build(command); return CommandAction.build(command); } - case "None" -> { - return new NoneAction<>(); + case CONSOLE_COMMAND -> { + String command = Objects.requireNonNull(section.getString("Command"), "Action.Command is null"); + return ConsoleCommandAction.build(command); } default -> throw new IllegalArgumentException("Unknown Action Type: " + type); } } - private T actor; + protected T actor; /** * The type of action */ @@ -44,35 +49,49 @@ public static Action fromConfigurationSection(ConfigurationSection secti * Runs the action. * Its implementation is left to the child class. */ - protected abstract void run(); + public abstract void run(); /** - * Updates the actor + * Updates the actor and runs the action. + * A quick way to perform an action on an actor. * * @param actor The actor to update + * @param The type of entity to perform the action on */ - protected void updateActor(T actor) { - this.actor = actor; + public void perform(U actor) { + if (updatesActor()) { + Action updatedAction = updateActor(actor); + updatedAction.run(); + } else { + run(); + } } /** - * Performs the action + * Modifies the action, updates the actor and runs the action. + * Will modify the action before performing it. + * A quick way to perform an action on an actor. * - * @param entity The entity to perform the action on + * @param actor The actor to update + * @param function The function to modify the action + * @param The type of entity to perform the action on */ - public void perform(@Nullable T entity) { + public void perform(U actor, Function function) { + Action modify = modify(function); if (updatesActor()) { - updateActor(entity); + Action updatedAction = modify.updateActor(actor); + updatedAction.run(); + } else { + modify.run(); } - run(); } /** - * Performs the action + * Updates the actor + * + * @param actor The actor to update */ - public void perform() { - perform(null); - } + public abstract Action updateActor(U actor); /** * Saves the action to a configuration section @@ -89,11 +108,9 @@ public T getActor() { } /** - * @return If the action updates the actor + * @return If the action updates the actor whenever it is performed */ - public boolean updatesActor() { - return true; - } + public abstract boolean updatesActor(); /** * @return If the action is asynchronous @@ -108,4 +125,6 @@ public boolean isAsync() { public ActionType getActionType() { return actionType; } -} + + public abstract Action modify(Function modifier); +} \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/action/ActionType.java b/src/main/java/us/mytheria/bloblib/action/ActionType.java index 000fc34b..b9ed0aa2 100644 --- a/src/main/java/us/mytheria/bloblib/action/ActionType.java +++ b/src/main/java/us/mytheria/bloblib/action/ActionType.java @@ -5,9 +5,9 @@ */ public enum ActionType { /** - * No action. + * A command that will perform with a null actor. */ - NONE, + NO_ACTOR, /** * A command that is run by the actor. */ diff --git a/src/main/java/us/mytheria/bloblib/action/CommandAction.java b/src/main/java/us/mytheria/bloblib/action/CommandAction.java index d97dc7db..346a23da 100644 --- a/src/main/java/us/mytheria/bloblib/action/CommandAction.java +++ b/src/main/java/us/mytheria/bloblib/action/CommandAction.java @@ -5,6 +5,7 @@ import org.bukkit.entity.Entity; import java.util.Objects; +import java.util.function.Function; /** * @param The type of entity this action is for @@ -15,7 +16,7 @@ * Entity. */ public class CommandAction extends Action { - private String command; + private final String command; /** * Creates a new CommandAction @@ -38,19 +39,26 @@ private CommandAction(String command) { * Runs the command as the actor */ @Override - protected void run() { + public void run() { Bukkit.dispatchCommand(getActor(), command); } /** - * Updates the actor + * Updates the actor. + * This will return a new instance of the action. * * @param actor The actor to update to */ - public void updateActor(T actor) { - super.updateActor(actor); - if (actor != null) - command = command.replace("%actor%", actor.getName()); + @Override + public CommandAction updateActor(U actor) { + if (actor != null) { + String updatedCommand = command.replace("%actor%", actor.getName()); + CommandAction updatedAction = new CommandAction<>(updatedCommand); + updatedAction.actor = actor; + return updatedAction; + } else { + throw new IllegalArgumentException("Actor cannot be null"); + } } /** @@ -63,4 +71,22 @@ public void save(ConfigurationSection section) { section.set("Command", command); section.set("Type", "ActorCommand"); } + + /** + * Modifies the command. + * This will return a new instance of the action. + * + * @param modifier The modifier to use + * @return The new CommandAction + */ + @Override + public CommandAction modify(Function modifier) { + String newCommand = modifier.apply(command); + return new CommandAction<>(newCommand); + } + + @Override + public boolean updatesActor() { + return true; + } } diff --git a/src/main/java/us/mytheria/bloblib/action/ConsoleCommandAction.java b/src/main/java/us/mytheria/bloblib/action/ConsoleCommandAction.java index 78885160..0b5d2fdb 100644 --- a/src/main/java/us/mytheria/bloblib/action/ConsoleCommandAction.java +++ b/src/main/java/us/mytheria/bloblib/action/ConsoleCommandAction.java @@ -5,13 +5,14 @@ import org.bukkit.entity.Entity; import java.util.Objects; +import java.util.function.Function; /** * @param The type of entity this action is for * @author anjoismysign */ public class ConsoleCommandAction extends Action { - private String command; + private final String command; public static ConsoleCommandAction build(String command) { Objects.requireNonNull(command); @@ -24,14 +25,30 @@ private ConsoleCommandAction(String command) { } @Override - protected void run() { + public void run() { Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command); } - public void updateActor(T actor) { - super.updateActor(actor); - if (actor != null) - command = command.replace("%actor%", actor.getName()); + /** + * Updates the actor. + * This will return a new instance of the action. + * + * @param actor The actor to update to + */ + @Override + public ConsoleCommandAction updateActor(U actor) { + if (actor != null) { + String updatedCommand = command.replace("%actor%", actor.getName()); + ConsoleCommandAction updatedAction = new ConsoleCommandAction<>(updatedCommand); + updatedAction.actor = actor; + return updatedAction; + } else { + if (actionType != ActionType.NO_ACTOR) { + throw new IllegalArgumentException("Actor cannot be null"); + } else { + return new ConsoleCommandAction<>(command); + } + } } @Override @@ -39,4 +56,23 @@ public void save(ConfigurationSection section) { section.set("Command", command); section.set("Type", "ConsoleCommand"); } + + /** + * Modifies the command. + * This will return a new instance of the action. + * + * @param modifier The modifier to use + * @return The new CommandAction + */ + @Override + public ConsoleCommandAction modify(Function modifier) { + String newCommand = modifier.apply(command); + return new ConsoleCommandAction<>(newCommand); + } + + + @Override + public boolean updatesActor() { + return true; + } } diff --git a/src/main/java/us/mytheria/bloblib/action/NoneAction.java b/src/main/java/us/mytheria/bloblib/action/NoneAction.java deleted file mode 100644 index d6a5b350..00000000 --- a/src/main/java/us/mytheria/bloblib/action/NoneAction.java +++ /dev/null @@ -1,22 +0,0 @@ -package us.mytheria.bloblib.action; - -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.Entity; - -public class NoneAction extends Action { - public NoneAction() { - this.actionType = ActionType.NONE; - } - - @Override - protected void run() { - } - - public void updateActor(T actor) { - } - - @Override - public void save(ConfigurationSection section) { - section.set("Type", "None"); - } -} diff --git a/src/main/java/us/mytheria/bloblib/api/BlobLibActionAPI.java b/src/main/java/us/mytheria/bloblib/api/BlobLibActionAPI.java new file mode 100644 index 00000000..43d9bb05 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/api/BlobLibActionAPI.java @@ -0,0 +1,45 @@ +package us.mytheria.bloblib.api; + +import org.bukkit.entity.Entity; +import org.jetbrains.annotations.Nullable; +import us.mytheria.bloblib.BlobLib; +import us.mytheria.bloblib.action.Action; +import us.mytheria.bloblib.managers.ActionManager; + +public class BlobLibActionAPI { + private static BlobLibActionAPI instance; + private final BlobLib plugin; + + private BlobLibActionAPI(BlobLib plugin) { + this.plugin = plugin; + } + + public static BlobLibActionAPI getInstance(BlobLib plugin) { + if (instance == null) { + if (plugin == null) + throw new NullPointerException("injected dependency is null"); + BlobLibActionAPI.instance = new BlobLibActionAPI(plugin); + } + return instance; + } + + public static BlobLibActionAPI getInstance() { + return getInstance(null); + } + + /** + * @return The action manager + */ + public ActionManager getActionManager() { + return plugin.getActionManager(); + } + + /** + * @param key The key of the action + * @return The action + */ + @Nullable + public Action getAction(String key) { + return getActionManager().getAction(key); + } +} diff --git a/src/main/java/us/mytheria/bloblib/api/BlobLibDisguiseAPI.java b/src/main/java/us/mytheria/bloblib/api/BlobLibDisguiseAPI.java new file mode 100644 index 00000000..456ce991 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/api/BlobLibDisguiseAPI.java @@ -0,0 +1,45 @@ +package us.mytheria.bloblib.api; + +import us.mytheria.bloblib.BlobLib; +import us.mytheria.bloblib.disguises.Disguiser; + +public class BlobLibDisguiseAPI { + private static BlobLibDisguiseAPI instance; + private final BlobLib plugin; + + private BlobLibDisguiseAPI(BlobLib plugin) { + this.plugin = plugin; + } + + public static BlobLibDisguiseAPI getInstance(BlobLib plugin) { + if (instance == null) { + if (plugin == null) + throw new NullPointerException("injected dependency is null"); + BlobLibDisguiseAPI.instance = new BlobLibDisguiseAPI(plugin); + } + return instance; + } + + public static BlobLibDisguiseAPI getInstance() { + return getInstance(null); + } + + /** + * Will return the Disguiser implementation + * WARNING! This method will throw an IllegalStateException if called before the world loads! + *

+ * Disguiser interface was made in order to support/cover + * a wide range of disguise plugins while at the same time + * allowing disguising methods to be called without failing + * fast! + *

+ * For future reference, if you want to use the Disguiser, + * be sure to not cache it in a variable, as we might see + * Plugman being updated :D + * + * @return the implementation + */ + public Disguiser getDisguiser() { + return plugin.getDisguiseManager().getDisguiser(); + } +} diff --git a/src/main/java/us/mytheria/bloblib/api/BlobLibEconomyAPI.java b/src/main/java/us/mytheria/bloblib/api/BlobLibEconomyAPI.java new file mode 100644 index 00000000..a8e0b7b0 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/api/BlobLibEconomyAPI.java @@ -0,0 +1,129 @@ +package us.mytheria.bloblib.api; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import us.mytheria.bloblib.BlobLib; +import us.mytheria.bloblib.vault.multieconomy.ElasticEconomy; + +public class BlobLibEconomyAPI { + private static BlobLibEconomyAPI instance; + private final BlobLib plugin; + + private BlobLibEconomyAPI(BlobLib plugin) { + this.plugin = plugin; + } + + public static BlobLibEconomyAPI getInstance(BlobLib plugin) { + if (instance == null) { + if (plugin == null) + throw new NullPointerException("injected dependency is null"); + BlobLibEconomyAPI.instance = new BlobLibEconomyAPI(plugin); + } + return instance; + } + + public static BlobLibEconomyAPI getInstance() { + return getInstance(null); + } + + /** + * @return The ElasticEconomy + */ + public ElasticEconomy getElasticEconomy() { + ElasticEconomy economy = plugin.getVaultManager().getElasticEconomy(); + if (economy.isAbsent()) + Bukkit.getLogger().severe("ElasticEconomy is not present. This is " + + "because there is no legacy Economy provider nor a MultiEconomy provider..."); + return economy; + } + + /** + * Formats the given amount of cash. + * + * @param amount Amount to format + * @return Formatted amount + * @deprecated Use {@link #getElasticEconomy()} instead + */ + @Deprecated + public String format(double amount) { + return plugin.getVaultManager().getVaultEconomyWorker().format(amount); + } + + /** + * Deposit an amount to a player - DO NOT USE NEGATIVE AMOUNTS + * + * @param player to deposit to + * @param amount amount to deposit + * @deprecated Use {@link #getElasticEconomy()} instead + */ + @Deprecated + public void addCash(Player player, double amount) { + plugin.getVaultManager().getVaultEconomyWorker().addCash(player, amount); + } + + /** + * Withdraws an amount to a player - DO NOT USE NEGATIVE AMOUNTS + * + * @param player to deposit to + * @param amount amount to deposit + * @deprecated Use {@link #getElasticEconomy()} instead + */ + @Deprecated + public void withdrawCash(Player player, double amount) { + plugin.getVaultManager().getVaultEconomyWorker().withdrawCash(player, amount); + } + + /** + * Accepts negative values. + * Be sure that vault implementation supports negative values. + * + * @param player to deposit to + * @param amount amount to deposit + * @deprecated Use {@link #getElasticEconomy()} instead + */ + @Deprecated + public void setCash(Player player, double amount) { + plugin.getVaultManager().getVaultEconomyWorker().setCash(player, amount); + } + + /** + * Checks if a player has a certain amount of money + * + * @param player to check + * @param amount amount to check + * @return true if player has amount + * @deprecated Use {@link #getElasticEconomy()} instead + */ + @Deprecated + public boolean hasCashAmount(Player player, double amount) { + return plugin.getVaultManager().getVaultEconomyWorker().hasCashAmount(player, amount); + } + + /** + * Gets the amount of money a player has + * + * @param player to check + * @return amount of money + * @deprecated Use {@link #getElasticEconomy()} instead + */ + @Deprecated + public double getCash(Player player) { + return plugin.getVaultManager().getVaultEconomyWorker().getCash(player); + } + + /** + * @return true if a vault economy provider plugin is being used + * @deprecated Use {@link #getElasticEconomy()} instead + */ + @Deprecated + public boolean hasVaultEconomyProvider() { + return plugin.getVaultManager().isVaultEcoInstalled(); + } + + /** + * @return true if a vault permission provider plugin is being used + */ + public boolean hasVaultPermissionsProvider() { + return plugin.getVaultManager().isVaultPermsInstalled(); + } +} diff --git a/src/main/java/us/mytheria/bloblib/api/BlobLibHologramAPI.java b/src/main/java/us/mytheria/bloblib/api/BlobLibHologramAPI.java new file mode 100644 index 00000000..a1bc2646 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/api/BlobLibHologramAPI.java @@ -0,0 +1,79 @@ +package us.mytheria.bloblib.api; + +import org.bukkit.Location; +import us.mytheria.bloblib.BlobLib; + +import java.util.List; + +public class BlobLibHologramAPI { + private static BlobLibHologramAPI instance; + private final BlobLib plugin; + + private BlobLibHologramAPI(BlobLib plugin) { + this.plugin = plugin; + } + + public static BlobLibHologramAPI getInstance(BlobLib plugin) { + if (instance == null) { + if (plugin == null) + throw new NullPointerException("injected dependency is null"); + BlobLibHologramAPI.instance = new BlobLibHologramAPI(plugin); + } + return instance; + } + + public static BlobLibHologramAPI getInstance() { + return getInstance(null); + } + + /** + * Creates a hologram + * + * @param name name of hologram + * @param location Bukkit's Location of hologram + * @param lines lines of hologram + */ + public void createHologram(String name, Location location, List lines) { + plugin.getHologramManager().create(name, location, lines); + } + + /** + * Creates a hologram + * + * @param name name of hologram + * @param location Bukkit's Location of hologram + * @param lines lines of hologram + * @param saveToConfig if true, hologram will be saved in configuration + */ + public void createHologram(String name, Location location, List lines, boolean saveToConfig) { + plugin.getHologramManager().create(name, location, lines, saveToConfig); + } + + /** + * Updates a hologram + * + * @param name name of hologram + */ + public void updateHologram(String name) { + plugin.getHologramManager().update(name); + } + + /** + * Deletes a hologram + * + * @param name name of hologram + */ + public void removeHologram(String name) { + plugin.getHologramManager().remove(name); + } + + /** + * Sets a hologram's lines + * + * @param name name of hologram + * @param lines lines of hologram + */ + public void setHologramLines(String name, List lines) { + plugin.getHologramManager().setLines(name, lines); + } +} diff --git a/src/main/java/us/mytheria/bloblib/api/BlobLibInventoryAPI.java b/src/main/java/us/mytheria/bloblib/api/BlobLibInventoryAPI.java new file mode 100644 index 00000000..e08c2540 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/api/BlobLibInventoryAPI.java @@ -0,0 +1,128 @@ +package us.mytheria.bloblib.api; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import us.mytheria.bloblib.BlobLib; +import us.mytheria.bloblib.entities.inventory.*; +import us.mytheria.bloblib.managers.InventoryManager; +import us.mytheria.bloblib.managers.MetaInventoryShard; + +import java.io.File; +import java.util.Optional; + +public class BlobLibInventoryAPI { + private static BlobLibInventoryAPI instance; + private final BlobLib plugin; + + private BlobLibInventoryAPI(BlobLib plugin) { + this.plugin = plugin; + } + + public static BlobLibInventoryAPI getInstance(BlobLib plugin) { + if (instance == null) { + if (plugin == null) + throw new NullPointerException("injected dependency is null"); + BlobLibInventoryAPI.instance = new BlobLibInventoryAPI(plugin); + } + return instance; + } + + public static BlobLibInventoryAPI getInstance() { + return getInstance(null); + } + + /** + * @return The inventory manager + */ + public InventoryManager getInventoryManager() { + return plugin.getInventoryManager(); + } + + /** + * @param key Key that points to the carrier + * @return The carrier if found. null otherwise + */ + @Nullable + public InventoryBuilderCarrier getInventoryBuilderCarrier(String key) { + return getInventoryManager().getInventoryBuilderCarrier(key); + } + + /** + * Will search for an InventoryBuilderCarrier with the given key. + * If found, will attempt to build the inventory. + * + * @param key Key that points to the inventory + * @return The inventory + */ + @Nullable + public BlobInventory getBlobInventory(String key) { + return getInventoryManager().getInventory(key); + } + + /** + * @param key Key that points to the carrier + * @return The carrier if found. null otherwise + */ + @Nullable + public InventoryBuilderCarrier getMetaInventoryBuilderCarrier(String key) { + return getInventoryManager().getMetaInventoryBuilderCarrier(key); + } + + /** + * Will search for an InventoryBuilderCarrier with the given key. + * If found, will attempt to build the inventory. + * + * @param key Key that points to the inventory + * @return The inventory + */ + @Nullable + public MetaBlobInventory getMetaBlobInventory(String key) { + return getInventoryManager().getMetaInventory(key); + } + + /** + * Attempts to get a MetaInventoryShard from the given type. + * If not found, will return an empty optional. + * MUST BE SURE Optional#isPresent == true BEFORE CALLING get() ON IT! + * + * @param type The type of the shard + * @return The shard if found, otherwise an empty optional + */ + @NotNull + public Optional hasMetaInventoryShard(String type) { + return Optional.ofNullable(getInventoryManager().getMetaInventoryShard(type)); + } + + /** + * Retrieves a file from the inventories' directory. + * + * @return The inventories file + */ + @NotNull + public File getInventoriesDirectory() { + return plugin.getFileManager().inventoriesDirectory(); + } + + /** + * @return The inventories file path + */ + @NotNull + public String getInventoriesFilePath() { + return plugin.getFileManager().inventoriesDirectory().getPath(); + } + + /** + * Attempts to build an inventory from the given file name. + * If the inventory is not found, a NullPointerException is thrown. + * + * @param fileName The file name + * @return The inventory + */ + public BlobInventory buildInventory(String fileName) { + BlobInventory inventory = getInventoryManager().cloneInventory(fileName); + if (inventory == null) { + throw new NullPointerException("Inventory '" + fileName + "' not found"); + } + return inventory; + } +} diff --git a/src/main/java/us/mytheria/bloblib/api/BlobLibListenerAPI.java b/src/main/java/us/mytheria/bloblib/api/BlobLibListenerAPI.java new file mode 100644 index 00000000..4c8a0e73 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/api/BlobLibListenerAPI.java @@ -0,0 +1,100 @@ +package us.mytheria.bloblib.api; + +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; +import us.mytheria.bloblib.BlobLib; +import us.mytheria.bloblib.entities.BlobEditor; +import us.mytheria.bloblib.entities.inventory.VariableSelector; +import us.mytheria.bloblib.entities.listeners.*; + +import java.util.function.Consumer; + +public class BlobLibListenerAPI { + private static BlobLibListenerAPI instance; + private final BlobLib plugin; + + private BlobLibListenerAPI(BlobLib plugin) { + this.plugin = plugin; + } + + public static BlobLibListenerAPI getInstance(BlobLib plugin) { + if (instance == null) { + if (plugin == null) + throw new NullPointerException("injected dependency is null"); + BlobLibListenerAPI.instance = new BlobLibListenerAPI(plugin); + } + return instance; + } + + public static BlobLibListenerAPI getInstance() { + return getInstance(null); + } + + /** + * Adds a new chat listener + * + * @param player The player + * @param timeout The timeout + * @param consumer The consumer. The String is the message sent by the player + * @param timeoutMessageKey The timeout message key + * @param timerMessageKey The timer message key + */ + public void addChatListener(Player player, long timeout, Consumer consumer, + String timeoutMessageKey, String timerMessageKey) { + plugin.getChatManager().addChatListener(player, + BlobChatListener.smart(player, timeout, consumer, timeoutMessageKey, timerMessageKey)); + } + + /** + * Adds a new drop listener + * + * @param player The player + * @param consumer The consumer. The ItemStack is the item dropped. + * @param timerMessageKey The timer message key + */ + public void addDropListener(Player player, Consumer consumer, + String timerMessageKey) { + plugin.getDropListenerManager().addDropListener(player, + BlobDropListener.smart(player, consumer, timerMessageKey)); + } + + /** + * Adds a new position listener + * + * @param player The player + * @param timeout The timeout + * @param consumer The consumer. The Block is the block clicked. + * @param timeoutMessageKey The timeout message key + * @param timerMessageKey The timer message key + */ + public void addPositionListener(Player player, long timeout, Consumer consumer, + String timeoutMessageKey, String timerMessageKey) { + plugin.getPositionManager().addPositionListener(player, + BlobSelPosListener.smart(player, timeout, consumer, timeoutMessageKey, timerMessageKey)); + } + + /** + * Adds a new selector listener + * + * @param player The player + * @param consumer The consumer. The argument is the item selected. + * @param timerMessageKey The timer message key + * @param selector The selector + * @param The type of the selector + */ + public void addSelectorListener(Player player, Consumer consumer, + @Nullable String timerMessageKey, + VariableSelector selector) { + plugin.getSelectorManager().addSelectorListener(player, + BlobSelectorListener.wise(player, consumer, timerMessageKey, selector)); + } + + public void addEditorListener(Player player, Consumer consumer, + String timerMessageKey, + BlobEditor editor) { + plugin.getSelectorManager().addEditorListener(player, + BlobEditorListener.wise(player, consumer, timerMessageKey, editor)); + } +} diff --git a/src/main/java/us/mytheria/bloblib/api/BlobLibMessageAPI.java b/src/main/java/us/mytheria/bloblib/api/BlobLibMessageAPI.java new file mode 100644 index 00000000..df2cd029 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/api/BlobLibMessageAPI.java @@ -0,0 +1,109 @@ +package us.mytheria.bloblib.api; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import us.mytheria.bloblib.BlobLib; +import us.mytheria.bloblib.entities.message.ReferenceBlobMessage; +import us.mytheria.bloblib.managers.MessageManager; + +import java.io.File; + +public class BlobLibMessageAPI { + private static BlobLibMessageAPI instance; + private final BlobLib plugin; + + private BlobLibMessageAPI(BlobLib plugin) { + this.plugin = plugin; + } + + public static BlobLibMessageAPI getInstance(BlobLib plugin) { + if (instance == null) { + if (plugin == null) + throw new NullPointerException("injected dependency is null"); + BlobLibMessageAPI.instance = new BlobLibMessageAPI(plugin); + } + return instance; + } + + public static BlobLibMessageAPI getInstance() { + return getInstance(null); + } + + /** + * @return The message manager + */ + public MessageManager getMessageManager() { + return plugin.getMessageManager(); + } + + /** + * @return The messages file + */ + @NotNull + public File getMessagesDirectory() { + return plugin.getFileManager().messagesDirectory(); + } + + /** + * @return The messages file path + */ + @NotNull + public String getMessagesFilePath() { + return plugin.getFileManager().messagesDirectory().getPath(); + } + + /** + * @param key The key of the message + * @return The message + */ + @Nullable + public ReferenceBlobMessage getMessage(String key) { + return getMessageManager().getMessage(key); + } + + /** + * @param key The key of the message + * @param locale The locale of the message + * @return The message + */ + @Nullable + public ReferenceBlobMessage getMessage(String key, String locale) { + return getMessageManager().getMessage(key, locale); + } + + /** + * @param key The key of the message + * @param locale The locale of the message + * @return The message, or the default message if not found + */ + @Nullable + public ReferenceBlobMessage getLocaleMessageOrDefault(String key, String locale) { + ReferenceBlobMessage localeMessage = getMessageManager().getMessage(key, locale); + if (localeMessage != null) + return localeMessage; + return getMessageManager().getMessage(key); + } + + /** + * Gets the locale of the player and returns the message. + * + * @param key The key of the message + * @param player The player to get the locale from + * @return The message, or the default message if not found + */ + @Nullable + public ReferenceBlobMessage getLocaleMessageOrDefault(String key, Player player) { + String locale = player.getLocale(); + return getLocaleMessageOrDefault(key, locale); + } + + /** + * @param key The key of the message + * @param player The player to send the message to + */ + public void sendMessage(String key, Player player) { + getMessageManager().send(player, key); + } + +} diff --git a/src/main/java/us/mytheria/bloblib/api/BlobLibPermissionAPI.java b/src/main/java/us/mytheria/bloblib/api/BlobLibPermissionAPI.java new file mode 100644 index 00000000..b86d0864 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/api/BlobLibPermissionAPI.java @@ -0,0 +1,106 @@ +package us.mytheria.bloblib.api; + +import org.bukkit.entity.Player; +import us.mytheria.bloblib.BlobLib; + +public class BlobLibPermissionAPI { + private static BlobLibPermissionAPI instance; + private final BlobLib plugin; + + private BlobLibPermissionAPI(BlobLib plugin) { + this.plugin = plugin; + } + + public static BlobLibPermissionAPI getInstance(BlobLib plugin) { + if (instance == null) { + if (plugin == null) + throw new NullPointerException("injected dependency is null"); + BlobLibPermissionAPI.instance = new BlobLibPermissionAPI(plugin); + } + return instance; + } + + public static BlobLibPermissionAPI getInstance() { + return getInstance(null); + } + + /** + * Add permission to a player ONLY for the world the player is currently on. + * This is a world-specific operation, if you want to add global permission + * you must explicitly use NULL for the world. + * + * @param player The player to add the permission to + * @param permission The permission to add + * @return true if the permission was added, false if the player already had the permission + */ + public boolean addPermission(Player player, String permission) { + return plugin.getVaultManager().getVaultPermissionsWorker().addPermission(player, permission); + } + + /** + * Remove permission from a player. + * Will attempt to remove permission from the player on the player's + * current world. This is NOT a global operation. + * + * @param player The player to remove the permission from + * @param permission The permission to remove + * @return true if the permission was removed, false if the player did not have the permission + */ + public boolean removePermission(Player player, String permission) { + return plugin.getVaultManager().getVaultPermissionsWorker().removePermission(player, permission); + } + + /** + * Add permission to a player. Supports NULL value for World if the permission + * system registered supports global permissions. But May return odd values if + * the servers registered permission system does not have a global permission store. + * + * @param player The player to add the permission to + * @param permission The permission to add + * @param world The world to add the permission to (null for global) + * @return true if the permission was added, false if the player already had the permission + */ + public boolean addPermission(Player player, String permission, String world) { + return plugin.getVaultManager().getVaultPermissionsWorker().addPermission(player, permission, world); + } + + /** + * Remove permission from a player. Supports NULL value for World if the + * permission system registered supports global permissions. But May return + * odd values if the servers registered permission system does not have a + * global permission store. + * + * @param player The player to remove the permission from + * @param permission The permission to remove + * @param world The world to remove the permission from + * (null for global) + * @return true if the permission was removed, false if the player did not have the permission + */ + public boolean removePermission(Player player, String permission, String world) { + return plugin.getVaultManager().getVaultPermissionsWorker().removePermission(player, permission, world); + } + + /** + * Add permission to a player. Will attempt to add permission + * to the player on the player, GLOBALLY. + * + * @param player The player to add the permission to + * @param permission The permission to add + * @return true if the permission was added, false if the player already had the permission + */ + public boolean addGlobalPermission(Player player, String permission) { + return plugin.getVaultManager().getVaultPermissionsWorker().addGlobalPermission(player, permission); + } + + /** + * Remove permission from a player. Will attempt to remove permission + * from the player on the player, GLOBALLY. + * + * @param player The player to remove the permission from + * @param permission The permission to remove + * @return true if the permission was removed, false if the player did not have the permission + */ + public boolean removeGlobalPermission(Player player, String permission) { + return plugin.getVaultManager().getVaultPermissionsWorker().removeGlobalPermission(player, permission); + } +} diff --git a/src/main/java/us/mytheria/bloblib/api/BlobLibSoundAPI.java b/src/main/java/us/mytheria/bloblib/api/BlobLibSoundAPI.java new file mode 100644 index 00000000..18605840 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/api/BlobLibSoundAPI.java @@ -0,0 +1,72 @@ +package us.mytheria.bloblib.api; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import us.mytheria.bloblib.BlobLib; +import us.mytheria.bloblib.entities.message.BlobSound; +import us.mytheria.bloblib.managers.SoundManager; + +import java.io.File; + +public class BlobLibSoundAPI { + private static BlobLibSoundAPI instance; + private final BlobLib plugin; + + private BlobLibSoundAPI(BlobLib plugin) { + this.plugin = plugin; + } + + public static BlobLibSoundAPI getInstance(BlobLib plugin) { + if (instance == null) { + if (plugin == null) + throw new NullPointerException("injected dependency is null"); + BlobLibSoundAPI.instance = new BlobLibSoundAPI(plugin); + } + return instance; + } + + public static BlobLibSoundAPI getInstance() { + return getInstance(null); + } + + /** + * @return The sound manager + */ + public SoundManager getSoundManager() { + return plugin.getSoundManager(); + } + + /** + * @param key The key of the sound + * @return The sound + */ + public BlobSound getSound(String key) { + return getSoundManager().getSound(key); + } + + /** + * @param key The key of the sound + * @param player The player to play the sound + */ + public void playSound(String key, Player player) { + getSoundManager().play(player, key); + } + + + /** + * @return The sounds file + */ + @NotNull + public File getSoundsDirectory() { + return plugin.getFileManager().soundsDirectory(); + } + + /** + * @return The sounds file path + */ + @NotNull + public String getSoundsFilePath() { + return plugin.getFileManager().soundsDirectory().getPath(); + } + +} diff --git a/src/main/java/us/mytheria/bloblib/bukkit/BlobBlockState.java b/src/main/java/us/mytheria/bloblib/bukkit/BlobBlockState.java new file mode 100644 index 00000000..2f57963a --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/bukkit/BlobBlockState.java @@ -0,0 +1,92 @@ +package us.mytheria.bloblib.bukkit; + +import net.minecraft.core.BlockPosition; +import net.minecraft.world.level.GeneratorAccess; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.block.BlockState; +import org.bukkit.craftbukkit.v1_20_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R1.block.CraftBlockState; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.Field; +import java.util.Objects; + +/** + * A small utility class to allow updating a block state in a specific location + */ +public class BlobBlockState { + private final CraftBlockState blockState; + private final static Field positionField; + + static { + try { + positionField = CraftBlockState.class.getDeclaredField("position"); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } + } + + private final static Field worldField; + + static { + try { + worldField = CraftBlockState.class.getDeclaredField("world"); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } + } + + @NotNull + public static BlobBlockState of(BlockState blockState) { + return new BlobBlockState((CraftBlockState) blockState); + } + + private BlobBlockState(CraftBlockState blockState) { + this.blockState = blockState; + } + + /** + * Gets the implementation of BlockState + * + * @return the implementation of BlockState + */ + public CraftBlockState getImplementation() { + return blockState; + } + + /** + * Allows updating a block state in a specific location + * without changing the original block state's location. + * + * @param force true to forcefully set the state + * @param applyPhysics false to cancel updating physics on surrounding blocks + * @param location the location to update the block state at + * @return true if the update was successful, false otherwise + */ + public boolean update(boolean force, boolean applyPhysics, @NotNull Location location) { + CraftWorld world = (CraftWorld) Objects.requireNonNull(location.getWorld()); + GeneratorAccess oldAccess = world.getHandle(); + blockState.setWorldHandle(world.getHandle()); + BlockPosition blockPosition = new BlockPosition(location.getBlockX(), location.getBlockY(), location.getBlockZ()); + positionField.setAccessible(true); + worldField.setAccessible(true); + BlockPosition oldPosition; + CraftWorld oldWorld; + boolean update = false; + try { + oldPosition = (BlockPosition) positionField.get(blockState); + oldWorld = (CraftWorld) worldField.get(blockState); + positionField.set(blockState, blockPosition); + worldField.set(blockState, world); + update = blockState.update(force, applyPhysics); + worldField.set(blockState, oldWorld); + positionField.set(blockState, oldPosition); + } catch (IllegalAccessException ignored) { + Bukkit.getLogger().severe("Failed to update block state"); + } + positionField.setAccessible(false); + blockState.setWorldHandle(oldAccess); + return update; + } +} diff --git a/src/main/java/us/mytheria/bloblib/bungee/BlobMessageReceiveEvent.java b/src/main/java/us/mytheria/bloblib/bungee/BlobMessageReceiveEvent.java deleted file mode 100644 index f7e0d039..00000000 --- a/src/main/java/us/mytheria/bloblib/bungee/BlobMessageReceiveEvent.java +++ /dev/null @@ -1,60 +0,0 @@ -package us.mytheria.bloblib.bungee; - -import net.md_5.bungee.api.connection.Connection; -import net.md_5.bungee.api.connection.Server; -import net.md_5.bungee.api.plugin.Event; -import us.mytheria.bloblib.entities.message.BungeeMessage; - -import javax.annotation.Nullable; - -/** - * @author anjoismysign - *

- * @deprecated API is in early development - * state and is highly probable to be subject - * to change. - */ -@Deprecated -public class BlobMessageReceiveEvent extends Event { - private final BungeeMessage message; - private final Connection sender; - - /** - * Here a protocol example for BlobTycoon: - * BlobTycoon would be a Bungee plugin aswell. While being - * a Bungee plugin, it will listen. If message's key !equals - * "BlobTycoon" return. Switch message's type in order - * to know what to do with message's value. - * - * @param message the message - * @param connection the sender's connection - */ - public BlobMessageReceiveEvent(BungeeMessage message, Connection connection) { - this.message = message; - this.sender = connection; - } - - /** - * @return Message - */ - public BungeeMessage getMessage() { - return message; - } - - /** - * @return Sender's connection - */ - public Connection getSender() { - return sender; - } - - /** - * @return null if false, Server object otherwise - */ - @Nullable - public Server isSenderAServer() { - if (getSender() instanceof Server server) - return server; - return null; - } -} \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/bungee/BungeeSharedSerializableInstance.java b/src/main/java/us/mytheria/bloblib/bungee/BungeeSharedSerializableInstance.java new file mode 100644 index 00000000..902b04dd --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/bungee/BungeeSharedSerializableInstance.java @@ -0,0 +1,26 @@ +package us.mytheria.bloblib.bungee; + +public class BungeeSharedSerializableInstance { + private int count; + private final int maxCount; + + public BungeeSharedSerializableInstance(int maxCount) { + this.maxCount = maxCount; + count = 0; + } + + public int getCount() { + return count; + } + + public boolean increment() { + if (count >= maxCount) + return false; + count++; + return true; + } + + public void decrement() { + count--; + } +} diff --git a/src/main/java/us/mytheria/bloblib/bungee/PluginMessageEventListener.java b/src/main/java/us/mytheria/bloblib/bungee/PluginMessageEventListener.java deleted file mode 100644 index 2a2eb938..00000000 --- a/src/main/java/us/mytheria/bloblib/bungee/PluginMessageEventListener.java +++ /dev/null @@ -1,31 +0,0 @@ -package us.mytheria.bloblib.bungee; - -import net.md_5.bungee.api.connection.Connection; -import net.md_5.bungee.api.connection.Server; -import net.md_5.bungee.api.event.PluginMessageEvent; -import net.md_5.bungee.api.plugin.Listener; -import net.md_5.bungee.event.EventHandler; -import us.mytheria.bloblib.BlobLibBungee; -import us.mytheria.bloblib.entities.message.BungeeMessage; - -public class PluginMessageEventListener implements Listener { - private BlobLibBungee main; - - public PluginMessageEventListener() { - this.main = BlobLibBungee.getInstance(); - main.getProxy().getPluginManager().registerListener(main, this); - } - - @EventHandler - public void onPluginMessage(PluginMessageEvent e) { - if (!e.getTag().equals("BlobLib")) - return; - Connection connection = e.getSender(); - if (!(connection instanceof Server)) - return; - BungeeMessage message = BungeeMessage.deserialize(e.getData()); - if (message == null) - return; - main.getProxy().getPluginManager().callEvent(new BlobMessageReceiveEvent(message, connection)); - } -} diff --git a/src/main/java/us/mytheria/bloblib/bungee/ProxiedSharedSerializableManager.java b/src/main/java/us/mytheria/bloblib/bungee/ProxiedSharedSerializableManager.java new file mode 100644 index 00000000..66b86cf1 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/bungee/ProxiedSharedSerializableManager.java @@ -0,0 +1,275 @@ +package us.mytheria.bloblib.bungee; + +import org.bson.Document; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.Listener; +import org.bukkit.plugin.messaging.PluginMessageListener; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import us.mytheria.bloblib.entities.BlobCrudable; +import us.mytheria.bloblib.entities.DocumentDecorator; +import us.mytheria.bloblib.entities.SharedSerializable; +import us.mytheria.bloblib.managers.BlobPlugin; +import us.mytheria.bloblib.managers.Manager; +import us.mytheria.bloblib.managers.ManagerDirector; +import us.mytheria.bloblib.storage.BlobCrudManager; +import us.mytheria.bloblib.storage.IdentifierType; +import us.mytheria.bloblib.storage.StorageType; +import us.mytheria.bloblib.utilities.BlobCrudManagerFactory; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import java.util.function.Function; + +public class ProxiedSharedSerializableManager> + extends Manager implements Listener, PluginMessageListener { + protected final HashMap cache; + protected BlobCrudManager crudManager; + private final BlobPlugin plugin; + private final String tag; + private final Function generator; + private final @Nullable Function joinEvent; + private final @Nullable Function quitEvent; + + protected ProxiedSharedSerializableManager(ManagerDirector managerDirector, Function newBorn, + Function generator, + String crudableName, boolean logActivity, + @Nullable Function joinEvent, + @Nullable Function quitEvent) { + super(managerDirector); + plugin = managerDirector.getPlugin(); + tag = plugin.getName() + "-" + crudableName; + plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, tag); + plugin.getServer().getMessenger().registerIncomingPluginChannel(plugin, tag, this); + Bukkit.getPluginManager().registerEvents(this, plugin); + cache = new HashMap<>(); + this.generator = generator; + this.joinEvent = joinEvent; + this.quitEvent = quitEvent; + crudManager = BlobCrudManagerFactory.PLAYER(plugin, crudableName, newBorn, logActivity); + reload(); + } + + @Override + public void unload() { + saveAll(); + } + + @Override + public void reload() { + unload(); + } + +/* + @EventHandler + public void onJoin(PlayerJoinEvent e) { + Player player = e.getPlayer(); + UUID uuid = player.getUniqueId(); + CompletableFuture future = new CompletableFuture<>(); + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + if (player == null || !player.isOnline()) { + future.completeExceptionally(new NullPointerException("Player is null")); + return; + } + BlobCrudable crudable = crudManager.read(uuid.toString()); + future.complete(crudable); + }); + future.thenAccept(crudable -> { + if (player == null || !player.isOnline()) + return; + T applied = generator.apply(crudable); + serializables.put(uuid, applied); + if (joinEvent == null) + return; + Bukkit.getPluginManager().callEvent(joinEvent.apply(applied)); + }); + } + + @EventHandler + public void onQuit(PlayerQuitEvent e) { + Player player = e.getPlayer(); + UUID uuid = player.getUniqueId(); + Optional optional = isSharedSerializable(uuid); + if (optional.isEmpty()) + return; + T serializable = optional.get(); + if (quitEvent != null) + Bukkit.getPluginManager().callEvent(quitEvent.apply(serializable)); + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + crudManager.update(serializable.serializeAllAttributes()); + removeObject(uuid); + //Send Quit message + }); + } +*/ + + @Override + public void onPluginMessageReceived(@NotNull String channel, + @NotNull Player x, + byte[] bytes) { + DocumentDecorator decorator = DocumentDecorator.deserialize(bytes); + Document document = decorator.document(); + String action = document.getString("Action").toLowerCase(); + switch (action) { + case "initialize" -> { + String playerName = decorator.hasString("SharedSerializable#OwnerName") + .orElseThrow(); + Player player = Bukkit.getPlayer(playerName); + if (player == null) + throw new NullPointerException("Player not found, report to BlobLib developers!"); + String instanceId = decorator.hasString("SharedSerializable#UniqueId") + .orElseThrow(); + CompletableFuture future = new CompletableFuture<>(); + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + if (player == null || !player.isOnline()) { + future.completeExceptionally(new NullPointerException("Player is null")); + return; + } + BlobCrudable crudable = crudManager.read(instanceId); + future.complete(crudable); + }); + future.thenAccept(crudable -> { + if (player == null || !player.isOnline()) + return; + T applied = generator.apply(crudable); + cache.put(instanceId, applied); + //Send instantiationConfirmed message + Document send = new Document(); + send.put("Action", "instantiationConfirmed"); + send.put("ProxiedPlayer#UniqueName", player.getName()); + applied.join(player); + sendPluginMessage(player, send); + //Address JoinEvent + if (joinEvent == null) + return; + Bukkit.getPluginManager().callEvent(joinEvent.apply(applied)); + }); + } + case "join" -> { + String playerName = decorator.hasString("SharedSerializable#OwnerName") + .orElseThrow(); + Player player = Bukkit.getPlayer(playerName); + if (player == null) + throw new NullPointerException("Player not found, report to BlobLib developers!"); + String instanceId = decorator.hasString("SharedSerializable#UniqueId") + .orElseThrow(); + T instance = cache.get(instanceId); + if (instance == null) + throw new NullPointerException("Instance not found, report to BlobLib developers!"); + instance.join(player); + //Send instantiationConfirmed message + Document send = new Document(); + send.put("Action", "instantiationConfirmed"); + send.put("ProxiedPlayer#UniqueName", player.getName()); + instance.join(player); + sendPluginMessage(player, send); + //Address JoinEvent + if (joinEvent == null) + return; + Bukkit.getPluginManager().callEvent(joinEvent.apply(instance)); + } + } + } + + private void sendPluginMessage(Player player, Document document) { + player.sendPluginMessage(plugin, tag, new DocumentDecorator(document).serialize()); + } + + /** + * Adds the object to the cache. + * + * @param serializable The object to add. + * @return The previous object. Null if no previous object was mapped. + */ + @Nullable + public T addObject(T serializable) { + return cache.put(serializable.getIdentification(), serializable); + } + + /** + * Removes the object from the cache. + * + * @param serializable The object to remove. + * @return The object removed. Null if not found. + */ + @Nullable + public T removeObject(T serializable) { + return removeObject(serializable.getIdentification()); + } + + /** + * Removes the object from the cache. + * + * @param key The key of the object. + * @return The object removed. Null if not found. + */ + @Nullable + public T removeObject(String key) { + return cache.remove(key); + } + + public Optional isSharedSerializable(String id) { + return Optional.ofNullable(cache.get(id)); + } + + public void ifIsOnline(String id, Consumer consumer) { + Optional optional = isSharedSerializable(id); + optional.ifPresent(consumer); + } + + public void ifIsOnlineThenUpdateElse(String id, Consumer consumer, + Runnable runnable) { + Optional optional = isSharedSerializable(id); + boolean isPresent = optional.isPresent(); + if (isPresent) { + T serializable = optional.get(); + consumer.accept(serializable); + crudManager.update(serializable.serializeAllAttributes()); + } else { + runnable.run(); + } + } + + public void ifIsOnlineThenUpdate(String id, Consumer consumer) { + ifIsOnlineThenUpdateElse(id, consumer, () -> { + }); + } + + public CompletableFuture readAsynchronously(String key) { + CompletableFuture future = new CompletableFuture<>(); + Bukkit.getScheduler().runTaskAsynchronously(getPlugin(), () -> + future.complete(generator.apply(crudManager.read(key)))); + return future; + } + + public void readThenUpdate(String key, Consumer consumer) { + T serializable = generator.apply(crudManager.read(key)); + consumer.accept(serializable); + crudManager.update(serializable.serializeAllAttributes()); + } + + public boolean exists(String key) { + return crudManager.exists(key); + } + + private void saveAll() { + cache.values().forEach(serializable -> crudManager.update(serializable.serializeAllAttributes())); + } + + public Collection getAll() { + return cache.values(); + } + + public StorageType getStorageType() { + return crudManager.getStorageType(); + } + + public IdentifierType getIdentifierType() { + return crudManager.getIdentifierType(); + } +} diff --git a/src/main/java/us/mytheria/bloblib/bungee/SharedSerializableInstanceManager.java b/src/main/java/us/mytheria/bloblib/bungee/SharedSerializableInstanceManager.java new file mode 100644 index 00000000..5953c17e --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/bungee/SharedSerializableInstanceManager.java @@ -0,0 +1,204 @@ +package us.mytheria.bloblib.bungee; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import me.anjoismysign.anjo.entities.Uber; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.Connection; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.connection.Server; +import net.md_5.bungee.api.event.PluginMessageEvent; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.event.EventHandler; +import org.bson.Document; +import us.mytheria.bloblib.entities.DocumentDecorator; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +/** + * Manages instances that may be accessed by multiple servers. + * It will cache instances in case a player attempts to connect to an + * instance that is already running (maybe from a coop member) and + * will synchronize these. + * It also handles the instantiation of new instances and + * will connect players to the instance with the lowest count. + */ +public class SharedSerializableInstanceManager implements Listener { + private final Plugin blobBungeePlugin; + private final String tag; + private final int maxCount; + private final BiMap> cached; + private final Map gamemodeInstances; + private final Map> joining; + private final Map pending; + + protected SharedSerializableInstanceManager(Plugin blobBungeePlugin, + String pluginName, + String crudableName, + int maxCount) { + this.blobBungeePlugin = blobBungeePlugin; + this.tag = pluginName + "-" + crudableName; + this.maxCount = maxCount; + this.cached = HashBiMap.create(); + this.gamemodeInstances = new HashMap<>(); + this.joining = new HashMap<>(); + this.pending = new HashMap<>(); + this.blobBungeePlugin.getProxy().getServers().values().forEach(serverInfo -> { + if (!serverInfo.getName().contains(pluginName)) + return; + gamemodeInstances.put(serverInfo, new BungeeSharedSerializableInstance(maxCount)); + }); + } + + @EventHandler + public void onMessage(PluginMessageEvent event) { + if (!event.getTag().equals(tag)) + return; + Document document = DocumentDecorator.deserialize(event.getData()) + .document(); + Connection connection = event.getSender(); + if (!(connection instanceof Server origin)) + return; + String action = document.getString("Action").toLowerCase(); + switch (action) { + case "shutdown" -> { + ServerInfo info = origin.getInfo(); + gamemodeInstances.put(info, new BungeeSharedSerializableInstance(maxCount)); + cached.entrySet().removeIf(entry -> entry.getValue().equals(info)); + } + case "quit" -> { + String instanceId = document.getString("SharedSerializable#UniqueId"); + Set instances = cached.values().stream() + .filter(set -> set.contains(instanceId)) + .findFirst().orElse(null); + if (instances == null) + return; + ServerInfo info = cached.inverse().get(instances); + if (info == null) + throw new NullPointerException("Instance not cached"); + //Sends instanceClosed message to server + //Listener: ProxiedSharedSerializableManager + Document send = new Document(); + send.put("Action", "instanceClosed"); + send.put("SharedSerializable#UniqueId", instanceId); + info.sendData(tag, new DocumentDecorator(send).serialize()); + gamemodeInstances.get(info).decrement(); + cached.get(info).remove(instanceId); + } + case "joinconfirmed" -> { + String uniqueName = document.getString("ProxiedPlayer#UniqueName"); + joining.get(uniqueName).complete(null); + joining.remove(uniqueName); + pending.remove(uniqueName); + } + case "instantiationconfirmed" -> { + String uniqueName = document.getString("ProxiedPlayer#UniqueName"); + joining.get(uniqueName).complete(null); + joining.remove(uniqueName); + pending.remove(uniqueName); + String instanceId = document.getString("SharedSerializable#UniqueId"); + //Let know cached that instanceId is now in origin + ServerInfo info = origin.getInfo(); + Set instances = cached.get(info); + if (instances == null) + throw new NullPointerException("Instance not cached. Report BlobLib developers!"); + instances.add(instanceId); + } + case "hasinstance" -> { + String uniqueName = document.getString("ProxiedPlayer#UniqueName"); + ProxiedPlayer player = blobBungeePlugin.getProxy().getPlayer(uniqueName); + String instanceId = document.getString("SharedSerializable#UniqueId"); + Set instances = cached.values().stream() + .filter(set -> set.contains(instanceId)) + .findFirst().orElse(null); + if (instances == null) { + ServerInfo info = findLowestCountInstance(); + player.connect(info); + //Sends initialize message + //Listener: ProxiedSharedSerializableManager + Document send = new Document(); + send.put("Action", "initialize"); + send.put("SharedSerializable#OwnerName", player.getName()); + send.put("SharedSerializable#UniqueId", instanceId); + info.sendData(tag, new DocumentDecorator(send).serialize()); + /* + * Past 10 seconds, if future has not been completed (which means + * instantiation failed), then remove the player from the joining + * map and complete the future exceptionally. + * If successful, will increment the game mode instance count. + */ + CompletableFuture future = new CompletableFuture<>(); + future.thenRun(() -> gamemodeInstances.get(info).increment()); + joining.put(player.getName(), future); + pending.put(player.getName(), instanceId); + blobBungeePlugin.getProxy().getScheduler().schedule(blobBungeePlugin, () -> { + if (!future.isDone()) { + joining.remove(player.getName()); + pending.remove(player.getName()); + future.completeExceptionally(new RuntimeException("Future did not complete within 10 seconds")); + } + }, 10, TimeUnit.SECONDS); + return; + } + ServerInfo info = cached.inverse().get(instances); + if (player == null || info == null) + return; + if (!player.isConnected()) + return; + info.ping((ping, throwable) -> { + if (throwable != null) { + //Sends connectionError message to origin + //Listener: GameModeInstanceLobby + Document send = new Document(); + send.put("Action", "connectionError"); + send.put("SharedSerializable#OwnerName", player.getName()); + origin.sendData(tag, new DocumentDecorator(send).serialize()); + return; + } + player.connect(info); + //Sends join message + //Listener: ProxiedSharedSerializableManager + CompletableFuture future = new CompletableFuture<>(); + joining.put(player.getName(), CompletableFuture.completedFuture(null)); + pending.put(player.getName(), instanceId); + blobBungeePlugin.getProxy().getScheduler().schedule(blobBungeePlugin, () -> { + if (!future.isDone()) { + joining.remove(player.getName()); + pending.remove(player.getName()); + future.completeExceptionally(new RuntimeException("Future did not complete within 10 seconds")); + } + }, 10, TimeUnit.SECONDS); + future.thenRun(() -> { + gamemodeInstances.get(info).increment(); + joining.remove(player.getName()); + pending.remove(player.getName()); + }); + Document send = new Document(); + send.put("Action", "join"); + send.put("SharedSerializable#OwnerName", player.getName()); + send.put("SharedSerializable#UniqueId", instanceId); + info.sendData(tag, new DocumentDecorator(send).serialize()); + }); + } + default -> { + } + } + } + + private ServerInfo findLowestCountInstance() { + Uber lowestCountInstance = Uber.fly(); + Uber lowestCount = Uber.drive(Integer.MAX_VALUE); + gamemodeInstances.forEach((serverInfo, instance) -> { + if (instance.getCount() < lowestCount.thanks()) { + lowestCountInstance.talk(serverInfo); + lowestCount.talk(instance.getCount()); + } + }); + return lowestCountInstance.thanks(); + } +} diff --git a/src/main/java/us/mytheria/bloblib/command/BlobLibCmd.java b/src/main/java/us/mytheria/bloblib/command/BlobLibCmd.java index d0cb67fe..4e07a31d 100644 --- a/src/main/java/us/mytheria/bloblib/command/BlobLibCmd.java +++ b/src/main/java/us/mytheria/bloblib/command/BlobLibCmd.java @@ -1,13 +1,35 @@ package us.mytheria.bloblib.command; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import org.bukkit.ChatColor; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.command.TabCompleter; +import org.bukkit.plugin.PluginBase; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import us.mytheria.bloblib.BlobLib; +import us.mytheria.bloblib.api.BlobLibMessageAPI; +import us.mytheria.bloblib.entities.PluginUpdater; +import us.mytheria.bloblib.entities.message.BlobMessage; +import us.mytheria.bloblib.managers.BlobPlugin; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.ProtocolException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * @author anjoismysign @@ -43,17 +65,104 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String main.getMessageManager().getMessage("System.No-Permission").toCommandSender(sender); return true; } - if (args.length < 1) { + int length = args.length; + if (length < 1) { debug(sender); return true; } - String arg1 = args[0]; - if (arg1.equalsIgnoreCase("reload")) { - main.reload(); - main.getMessageManager().getMessage("System.Reload").toCommandSender(sender); - return true; + String arg1 = args[0].toLowerCase(); + switch (arg1) { + case "reload" -> { + main.reload(); + main.getMessageManager().getMessage("System.Reload").toCommandSender(sender); + return true; + } + case "update" -> { + PluginUpdater updater; + boolean isPlugin = false; + if (length > 1) { + String input = args[1]; + BlobPlugin plugin = main.getPluginManager().get(input); + if (plugin != null && plugin.getPluginUpdater() != null) { + updater = plugin.getPluginUpdater(); + isPlugin = true; + } else + updater = main.getBloblibupdater(); + } else + updater = main.getBloblibupdater(); + boolean successful = updater.download(); + if (!successful) + return true; + BlobMessage message = BlobLibMessageAPI.getInstance().getMessage("BlobLib.Updater-Successful"); + if (isPlugin) { + message.modder() + .replace("%randomColor%", main.getColorManager().randomColor().toString()) + .replace("%plugin%", updater.getPlugin().getName()) + .replace("%version%", updater.getLatestVersion()) + .get() + .toCommandSender(sender); + } else + message.modder() + .replace("%randomColor%", main.getColorManager().randomColor().toString()) + .replace("%plugin%", updater.getPlugin().getName()) + .replace("%version%", updater.getLatestVersion()) + .get() + .toCommandSender(sender); + return true; + } + case "download" -> { + if (length < 2) { + BlobLibMessageAPI.getInstance().getMessage("BlobLib.Download-Usage") + .toCommandSender(sender); + return true; + } else if (length < 3) { + BlobLibMessageAPI.getInstance().getMessage("BlobLib.Download-GitHub-Usage") + .toCommandSender(sender); + return true; + } else { + String provider = args[1]; + if (!provider.equalsIgnoreCase("GitHub")) { + sender.sendMessage(ChatColor.RED + "Currently supported providers: [ GitHub ]"); + return true; + } + String input = args[2]; + String[] split = input.split("/"); + if (split.length != 2) { + BlobLibMessageAPI.getInstance().getMessage("BlobLib.Download-GitHub-Usage") + .toCommandSender(sender); + return true; + } + String owner = split[0]; + String repo = split[1]; + RepositoryDownload download = downloadGitHub(owner, repo); + boolean successful = download.successful(); + if (successful) + BlobLibMessageAPI.getInstance().getMessage("BlobLib.Download-GitHub-Successful") + .modder() + .replace("%randomColor%", main.getColorManager().randomColor().toString()) + .replace("%fileName%", download.fileName()) + .get() + .toCommandSender(sender); + else { + DownloadError error = download.error(); + switch (error) { + case NO_CONNECTION -> + BlobLibMessageAPI.getInstance().getMessage("BlobLib.No-Connection") + .toCommandSender(sender); + case REPO_NOT_FOUND -> + BlobLibMessageAPI.getInstance().getMessage("BlobLib.Repository-Not-Found") + .toCommandSender(sender); + default -> sender.sendMessage(ChatColor.RED + "Could not download file"); + } + } + return true; + } + } + default -> { + debug(sender); + return true; + } } - return false; } catch (Exception e) { e.printStackTrace(); return false; @@ -68,6 +177,7 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String public void debug(CommandSender sender) { if (sender.hasPermission("bloblib.debug")) { sender.sendMessage("/bloblib reload"); + sender.sendMessage("/bloblib update"); } } @@ -84,12 +194,123 @@ public List onTabComplete(CommandSender sender, Command command, String if (command.getName().equalsIgnoreCase("bloblib")) { if (sender.hasPermission("blobblib.admin")) { List l = new ArrayList<>(); - if (args.length == 1) { - l.add("reload"); + int length = args.length; + switch (length) { + case 1 -> { + l.add("reload"); + l.add("update"); + l.add("download"); + } + case 2 -> { + String arg = args[0].toLowerCase(); + switch (arg) { + case "update" -> { + l.addAll(main.getPluginManager().values().stream() + .map(BlobPlugin::getPluginUpdater) + .filter(Objects::nonNull) + .filter(PluginUpdater::hasAvailableUpdate) + .map(PluginUpdater::getPlugin) + .map(PluginBase::getName) + .toList()); + } + case "download" -> { + l.add("GitHub"); + } + default -> { + } + } + } + default -> { + } } return l; } } return null; } + + private record RepositoryDownload(@Nullable String fileName, + @NotNull DownloadError error) { + private static RepositoryDownload FAIL(DownloadError error) { + return new RepositoryDownload(null, error); + } + + public boolean successful() { + return error == DownloadError.NONE; + } + } + + private enum DownloadError { + NO_CONNECTION, + REPO_NOT_FOUND, + NO_PERMISSION, + MALFORMED_URL, + PROTOCOL_ERROR, + NONE, + UNKNOWN + } + + /** + * Downloads a plugin from GitHub. It's expected that + * file does not exist in the plugins' folder. + * + * @param owner The owner of the repo + * @param repo The repo name + * @return the GitHubDownload object, null if unsuccessful + */ + @NotNull + private BlobLibCmd.RepositoryDownload downloadGitHub(String owner, String repo) { + String repoUrl = "https://api.github.com/repos/" + owner + "/" + repo + "/releases"; + URL url; + try { + url = new URL(repoUrl); + } catch (MalformedURLException e) { + return RepositoryDownload.FAIL(DownloadError.MALFORMED_URL); + } + HttpURLConnection connection; + try { + connection = (HttpURLConnection) url.openConnection(); + } catch (IOException e) { + return RepositoryDownload.FAIL(DownloadError.NO_CONNECTION); + } + try { + connection.setRequestMethod("GET"); + } catch (ProtocolException e) { + return RepositoryDownload.FAIL(DownloadError.PROTOCOL_ERROR); + } + BufferedReader reader; + try { + reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + } catch (IOException e) { + BlobLib.getAnjoLogger().singleError("Repo not found: " + repoUrl); + return RepositoryDownload.FAIL(DownloadError.REPO_NOT_FOUND); + } + StringBuilder response = new StringBuilder(); + String line; + try { + while ((line = reader.readLine()) != null) { + response.append(line); + } + reader.close(); + } catch (IOException e) { + return RepositoryDownload.FAIL(DownloadError.UNKNOWN); + } + Gson gson = new Gson(); + JsonArray releases = gson.fromJson(response.toString(), JsonArray.class); + JsonObject latestRelease = releases.get(0).getAsJsonObject(); + String latestUrl = latestRelease.get("assets").getAsJsonArray().get(0).getAsJsonObject().get("browser_download_url").getAsString(); + String fileName = latestUrl.substring(latestUrl.lastIndexOf("/") + 1); + try { + url = new URL(latestUrl); + } catch (MalformedURLException e) { + return RepositoryDownload.FAIL(DownloadError.MALFORMED_URL); + } + Path targetPath = Path.of("plugins", fileName); + try (InputStream inputStream = url.openStream()) { + Files.copy(inputStream, targetPath); + return new RepositoryDownload(fileName, DownloadError.NONE); + } catch (IOException e) { + return RepositoryDownload.FAIL(DownloadError.UNKNOWN); + } + } } diff --git a/src/main/java/us/mytheria/bloblib/disguises/Absent.java b/src/main/java/us/mytheria/bloblib/disguises/Absent.java new file mode 100644 index 00000000..f0109149 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/disguises/Absent.java @@ -0,0 +1,27 @@ +package us.mytheria.bloblib.disguises; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; + +public class Absent implements Disguiser { + private final DisguiseEngine engine; + + public Absent() { + engine = DisguiseEngine.NONE; + } + + + @Override + public DisguiseEngine getEngine() { + return engine; + } + + @Override + public void disguiseEntity(String disguise, Entity entity) { + } + + @Override + public void disguiseEntityForTarget(String disguise, Entity entity, + Player... target) { + } +} diff --git a/src/main/java/us/mytheria/bloblib/disguises/DisguiseEngine.java b/src/main/java/us/mytheria/bloblib/disguises/DisguiseEngine.java new file mode 100644 index 00000000..e87a5481 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/disguises/DisguiseEngine.java @@ -0,0 +1,6 @@ +package us.mytheria.bloblib.disguises; + +public enum DisguiseEngine { + LIBS_DISGUISES, + NONE +} diff --git a/src/main/java/us/mytheria/bloblib/disguises/DisguiseManager.java b/src/main/java/us/mytheria/bloblib/disguises/DisguiseManager.java new file mode 100644 index 00000000..958eb2b3 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/disguises/DisguiseManager.java @@ -0,0 +1,34 @@ +package us.mytheria.bloblib.disguises; + +import org.bukkit.Bukkit; + +public class DisguiseManager { + private boolean loaded; + private Disguiser disguiser; + + public DisguiseManager() { + loaded = false; + } + + /** + * Should not be run by plugins, only by BlobLib + */ + public void load() { + if (Bukkit.getPluginManager().getPlugin("LibsDisguises") != null) { + updateDisguiser(new LibsDisguises()); + } else { + updateDisguiser(new Absent()); + } + loaded = true; + } + + private void updateDisguiser(Disguiser disguiser) { + this.disguiser = disguiser; + } + + public Disguiser getDisguiser() { + if (!loaded) + throw new IllegalStateException("DisguiseManager loads after world load!"); + return this.disguiser; + } +} diff --git a/src/main/java/us/mytheria/bloblib/disguises/Disguiser.java b/src/main/java/us/mytheria/bloblib/disguises/Disguiser.java new file mode 100644 index 00000000..cf31fb5a --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/disguises/Disguiser.java @@ -0,0 +1,41 @@ +package us.mytheria.bloblib.disguises; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; + +public interface Disguiser { + /** + * Will return disguise engine used by this server + * + * @return Disguise engine + */ + DisguiseEngine getEngine(); + + /** + * Will disguise specific entity with provided disguise + * to all players. + * + * @param disguise Disguise to use + * @param entity Entity to disguise + */ + void disguiseEntity(String disguise, Entity entity); + + /** + * Will disguise specific entity with provided disguise + * to provided player/s only + * + * @param disguise Disguise to use + * @param entity Entity to disguise + * @param target Player to disguise to + */ + void disguiseEntityForTarget(String disguise, Entity entity, Player... target); + + /** + * True if server has disguise engine + * + * @return True if server has disguise engine + */ + default boolean hasEngine() { + return getEngine() != DisguiseEngine.NONE; + } +} diff --git a/src/main/java/us/mytheria/bloblib/disguises/LibsDisguises.java b/src/main/java/us/mytheria/bloblib/disguises/LibsDisguises.java new file mode 100644 index 00000000..f518d60c --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/disguises/LibsDisguises.java @@ -0,0 +1,46 @@ +package us.mytheria.bloblib.disguises; + +import me.libraryaddict.disguise.DisguiseAPI; +import me.libraryaddict.disguise.disguisetypes.Disguise; +import me.libraryaddict.disguise.utilities.parser.DisguiseParser; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; + +public class LibsDisguises implements Disguiser { + private final DisguiseEngine engine; + + public LibsDisguises() { + engine = DisguiseEngine.NONE; + } + + + @Override + public DisguiseEngine getEngine() { + return engine; + } + + @Override + public void disguiseEntity(String raw, Entity entity) { + Disguise disguise; + try { + disguise = DisguiseParser.parseDisguise(raw); + } catch (Throwable throwable) { + throwable.printStackTrace(); + return; + } + DisguiseAPI.disguiseToAll(entity, disguise); + } + + @Override + public void disguiseEntityForTarget(String raw, Entity entity, + Player... target) { + Disguise disguise; + try { + disguise = DisguiseParser.parseDisguise(raw); + } catch (Throwable throwable) { + throwable.printStackTrace(); + return; + } + DisguiseAPI.disguiseToPlayers(entity, disguise, target); + } +} diff --git a/src/main/java/us/mytheria/bloblib/floatingpet/FloatingPet.java b/src/main/java/us/mytheria/bloblib/displayentity/ArmorStandFloatingPet.java similarity index 72% rename from src/main/java/us/mytheria/bloblib/floatingpet/FloatingPet.java rename to src/main/java/us/mytheria/bloblib/displayentity/ArmorStandFloatingPet.java index df912b61..c6e07638 100644 --- a/src/main/java/us/mytheria/bloblib/floatingpet/FloatingPet.java +++ b/src/main/java/us/mytheria/bloblib/displayentity/ArmorStandFloatingPet.java @@ -1,4 +1,4 @@ -package us.mytheria.bloblib.floatingpet; +package us.mytheria.bloblib.displayentity; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -10,39 +10,47 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.scheduler.BukkitTask; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import us.mytheria.bloblib.BlobLib; +import java.util.Objects; +import java.util.Random; import java.util.UUID; -public class FloatingPet { +public class ArmorStandFloatingPet implements DisplayPet { private Particle particle; - private ArmorStand armorStand; + private ArmorStand entity; private Location location; private UUID owner; - private boolean activated, hasParticle, pauseLogic; + private boolean activated, pauseLogic; private ItemStack display; private String customName; - private FloatingPetAnimations animations; + private DisplayEntityAnimations animations; private BukkitTask logicTask; + private final EntityAnimationsCarrier animationsCarrier; /** * Creates a pet * * @param owner - the FloatingPet owner - * @param itemStack - the ItemStack to display + * @param itemStack - the ItemStack to itemStack * (must be an item or a block) - * @param particle - the Particle to display + * @param particle - the Particle to itemStack * @param customName - the CustomName of the pet * (if null will be used 'owner's Pet') */ - public FloatingPet(Player owner, ItemStack itemStack, @Nullable Particle particle, - @Nullable String customName) { + public ArmorStandFloatingPet(@NotNull Player owner, + @NotNull ItemStack itemStack, + @Nullable Particle particle, + @Nullable String customName, + @NotNull EntityAnimationsCarrier animationsCarrier) { + this.animationsCarrier = Objects.requireNonNull(animationsCarrier); Material type = itemStack.getType(); if (!type.isItem() && type.isBlock()) throw new IllegalArgumentException("ItemStack must be an item or a block"); this.pauseLogic = false; - setOwner(owner); + setOwner(owner.getUniqueId()); setDisplay(itemStack); setCustomName(customName); setParticle(particle); @@ -54,8 +62,10 @@ public FloatingPet(Player owner, ItemStack itemStack, @Nullable Particle particl */ public void spawn() { setActive(true); - Location loc = getOwner().getLocation().clone(); - loc.setX(loc.getX() - 1); + Location loc = findOwnerOrFail().getLocation().clone(); + Random random = new Random(); + loc.setX(loc.getX() + random.nextInt(3) - 1); + loc.setZ(loc.getZ() + random.nextInt(3) - 1); loc.setY(loc.getY() + 0.85); setLocation(loc); spawnArmorStand(loc); @@ -63,14 +73,14 @@ public void spawn() { private void spawnArmorStand(Location loc) { BlobLib plugin = BlobLib.getInstance(); - armorStand = createArmorStand(loc); + entity = createArmorStand(loc); setCustomName(getCustomName()); - Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> armorStand.setCustomNameVisible(true), 1); - Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> armorStand.setGravity(false), 3); - Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> armorStand.setSmall(true), 4); - Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> armorStand.setInvulnerable(true), 5); - armorStand.getEquipment().setHelmet(getDisplay()); - Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> armorStand.setVisible(false), 6); + Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> entity.setCustomNameVisible(true), 1); + Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> entity.setGravity(false), 3); + Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> entity.setSmall(true), 4); + Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> entity.setInvulnerable(true), 5); + entity.getEquipment().setHelmet(getDisplay()); + Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> entity.setVisible(false), 6); initAnimations(plugin); } @@ -83,21 +93,20 @@ private void spawnArmorStand(Location loc) { */ public void setCustomName(String customName) { this.customName = customName; - if (armorStand == null) + if (entity == null) return; - armorStand.setCustomNameVisible(true); - armorStand.setCustomName(customName); + entity.setCustomNameVisible(true); + entity.setCustomName(customName); } private void initAnimations(JavaPlugin plugin) { - animations = new FloatingPetAnimations(this, 0.5, 0.55, - 0.025, 0.2, -0.5); + animations = new DisplayEntityAnimations(this, animationsCarrier); initLogic(plugin); } private void initLogic(JavaPlugin plugin) { logicTask = Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, () -> { - Player owner = getOwner(); + Player owner = findOwner(); if (owner == null || !owner.isOnline()) { destroy(); return; @@ -105,9 +114,11 @@ private void initLogic(JavaPlugin plugin) { spawnParticles(); if (!isPauseLogic()) { double distance = Math.sqrt(Math.pow(getLocation().getX() - owner.getLocation().getX(), 2) + Math.pow(getLocation().getZ() - owner.getLocation().getZ(), 2)); - if (distance >= 2.5D || Math.abs(owner.getLocation().getY() - getLocation().getY()) > 1D) + if (distance >= animationsCarrier.teleportDistanceThreshold()) + teleport(owner.getLocation()); + else if (distance >= animationsCarrier.approachDistanceThreshold() || Math.abs(owner.getLocation().getY() + animationsCarrier.yOffset() - getLocation().getY()) > 1D) move(); - else if (distance <= 1.0D) { + else if (distance <= animationsCarrier.minimumDistance()) { moveAway(); } else { idle(); @@ -117,15 +128,15 @@ else if (distance <= 1.0D) { } private void moveAway() { - this.location = animations.moveAway(getOwner(), location); + this.location = animations.moveAway(findOwnerOrFail(), location); } private void move() { - this.location = animations.move(getOwner(), location); + this.location = animations.move(findOwnerOrFail(), location); } private void idle() { - this.location = animations.idle(getOwner(), location); + this.location = animations.idle(findOwnerOrFail(), location); } /** @@ -135,9 +146,9 @@ private void idle() { public void destroy() { setActive(false); Bukkit.getScheduler().cancelTask(logicTask.getTaskId()); - if (armorStand != null) { - armorStand.remove(); - armorStand = null; + if (entity != null) { + entity.remove(); + entity = null; } } @@ -155,8 +166,8 @@ public ItemStack getDisplay() { * * @return the owner */ - public Player getOwner() { - return Bukkit.getPlayer(owner); + public UUID getOwner() { + return owner; } /** @@ -164,8 +175,8 @@ public Player getOwner() { * * @param owner - the new owner */ - public void setOwner(Player owner) { - this.owner = owner.getUniqueId(); + public void setOwner(UUID owner) { + this.owner = Objects.requireNonNull(owner); } /** @@ -173,8 +184,8 @@ public void setOwner(Player owner) { * * @return the armorstand */ - public ArmorStand getArmorStand() { - return armorStand; + public ArmorStand getEntity() { + return entity; } /** @@ -183,31 +194,22 @@ public ArmorStand getArmorStand() { * @return if customName is null, returns 'owner's Pet', else returns customName */ public String getCustomName() { - String name = getOwner().getName() + "'s Pet"; + String name = findOwnerOrFail().getName() + "'s Pet"; if (this.customName != null) name = this.customName; return name; } - private void spawnParticles() { - if (!hasParticle) - return; - Location particleLoc = location.clone(); - particleLoc.setY(particleLoc.getY() + 0.7); - particleLoc.getWorld().spawnParticle(getParticle(), particleLoc, 0); - - } - /** * Teleports the pet * - * @param loc - the new location + * @param loc the new location */ public void teleport(Location loc) { - if (armorStand != null) { + if (entity != null) { loadChunks(location); loadChunks(loc); - armorStand.teleport(loc); + entity.teleport(loc); } setLocation(loc); } @@ -313,15 +315,14 @@ public Particle getParticle() { */ public void setParticle(Particle particle) { this.particle = particle; - this.hasParticle = particle != null; } /** * Sets the pet head * - * @param display - the new head + * @param itemStack - the new head */ - public void setDisplay(ItemStack display) { - this.display = display; + public void setDisplay(ItemStack itemStack) { + this.display = Objects.requireNonNull(itemStack); } } \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/displayentity/BlockDisplayFloatingPet.java b/src/main/java/us/mytheria/bloblib/displayentity/BlockDisplayFloatingPet.java new file mode 100644 index 00000000..9ef12cd5 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/displayentity/BlockDisplayFloatingPet.java @@ -0,0 +1,49 @@ +package us.mytheria.bloblib.displayentity; + +import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.BlockDisplay; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; +import us.mytheria.bloblib.BlobLib; + +public class BlockDisplayFloatingPet extends DisplayFloatingPet { + /** + * Creates a pet + * + * @param owner - the FloatingPet owner + * @param display - the display (like BlockData/ItemStack) + * (must be an item or a block) + * @param particle - the Particle to itemStack + * @param customName - the CustomName of the pet + * (if null will be used 'owner's Pet') + */ + public BlockDisplayFloatingPet(Player owner, BlockData display, + @Nullable Particle particle, + @Nullable String customName, + DisplayFloatingPetSettings settings) { + super(owner, display, particle, customName, settings); + } + + void spawnEntity(Location location) { + BlobLib plugin = BlobLib.getInstance(); + entity = (BlockDisplay) location.getWorld().spawnEntity(location, + EntityType.BLOCK_DISPLAY); + vehicle = (BlockDisplay) location.getWorld().spawnEntity(location, + EntityType.BLOCK_DISPLAY); + entity.setPersistent(false); + vehicle.setPersistent(false); + if (!vehicle.addPassenger(entity)) { + throw new RuntimeException("Failed to add passenger to vehicle"); + } + setCustomName(getCustomName()); + entity.setBlock(getDisplay()); + initAnimations(plugin); + } + + public void setDisplay(BlockData display) { + this.display = display; + } +} diff --git a/src/main/java/us/mytheria/bloblib/displayentity/DisplayEntity.java b/src/main/java/us/mytheria/bloblib/displayentity/DisplayEntity.java new file mode 100644 index 00000000..2fadf48d --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/displayentity/DisplayEntity.java @@ -0,0 +1,87 @@ +package us.mytheria.bloblib.displayentity; + +import org.bukkit.Location; +import org.bukkit.entity.Entity; + +/** + * A display entity is an entity which is not a real entity + * in terms such as not having a hitbox, having a very + * basic AI while allowing adding new custom entities + * without having to make use of resource packs. + * For example, FloatingPets are display entities. + * + * @param the entity type which is the representation + * of the FloatingDisplay in the minecraft world + * or in case of complex display entities, the + * root parent part/entity. + */ +public interface DisplayEntity { + + /** + * Spawns the entity. + */ + void spawn(); + + /** + * Similar to Entity#remove. + * Will mark the entity as deactivated and will remove the + * floating display entity. + */ + void destroy(); + + /** + * Returns the entity which is the representation + * of the FloatingDisplay in the minecraft world + * or in case of complex display entities, the + * root parent part/entity. + * + * @return the entity + */ + T getEntity(); + + /** + * Teleports the entity to the given location. + * + * @param location - the new location + */ + void teleport(Location location); + + /** + * Returns the location of the entity's entity. + * + * @return the location + */ + Location getLocation(); + + /** + * Returns if the entity is activated. + * If true, it means that it is spawned. + * If false, it means that it was called + * to be removed. + * + * @return if the entity is activated + */ + boolean isActive(); + + /** + * Returns if the entity logic is paused + * + * @return if the logic is paused + */ + boolean isPauseLogic(); + + /** + * Sets if the entity logic should be paused + * + * @param pauseLogic - if the logic should be paused + * if true, will make entity static/paused + */ + void setPauseLogic(boolean pauseLogic); + + /** + * Sets the entity display/ItemStack + * + * @param display - the new display + */ + void setDisplay(R display); +} diff --git a/src/main/java/us/mytheria/bloblib/floatingpet/FloatingPetAnimations.java b/src/main/java/us/mytheria/bloblib/displayentity/DisplayEntityAnimations.java similarity index 76% rename from src/main/java/us/mytheria/bloblib/floatingpet/FloatingPetAnimations.java rename to src/main/java/us/mytheria/bloblib/displayentity/DisplayEntityAnimations.java index 1a6ad999..03c3ebcd 100644 --- a/src/main/java/us/mytheria/bloblib/floatingpet/FloatingPetAnimations.java +++ b/src/main/java/us/mytheria/bloblib/displayentity/DisplayEntityAnimations.java @@ -1,4 +1,4 @@ -package us.mytheria.bloblib.floatingpet; +package us.mytheria.bloblib.displayentity; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -6,28 +6,42 @@ import org.bukkit.util.Vector; import us.mytheria.bloblib.BlobLib; -public class FloatingPetAnimations { +public class DisplayEntityAnimations { private static final BlobLib plugin = BlobLib.getInstance(); - private final double followSpeed, walkAwaySpeed, hoverSpeed, hoverMaxHeightCap, hoverMinHeightCap; + private final double followSpeed, walkAwaySpeed, hoverSpeed, hoverHeightCeiling, + hoverHeightFloor, yOffset; private double hoverVelocity, hoverHeight; - private final FloatingPet pet; + private final DisplayEntity pet; - public FloatingPetAnimations(FloatingPet pet, double followSpeed, double walkAwaySpeed, double hoverSpeed, - double hoverMaxHeightCap, double hoverMinHeightCap) { + public DisplayEntityAnimations(DisplayEntity pet, + double followSpeed, + double walkAwaySpeed, + double hoverSpeed, + double hoverHeightCeiling, + double hoverHeightFloor, + double yOffset) { this.pet = pet; this.followSpeed = followSpeed; this.walkAwaySpeed = walkAwaySpeed; this.hoverSpeed = hoverSpeed; - this.hoverMaxHeightCap = hoverMaxHeightCap; - this.hoverMinHeightCap = hoverMinHeightCap; + this.hoverHeightCeiling = hoverHeightCeiling; + this.hoverHeightFloor = hoverHeightFloor; this.hoverVelocity = hoverSpeed; this.hoverHeight = 0; + this.yOffset = yOffset; + } + + public DisplayEntityAnimations(DisplayEntity pet, + EntityAnimationsCarrier carrier) { + this(pet, carrier.followSpeed(), carrier.walkAwaySpeed(), carrier.hoverSpeed(), + carrier.hoverHeightCeiling(), carrier.hoverHeightFloor(), + carrier.yOffset()); } public Location move(Player player, Location loc) { Vector goal = vectorFromLocation(player.getLocation()); - goal.setY(goal.getY() + 0.75); + goal.setY(goal.getY() + yOffset); double distance = Math.sqrt(Math.pow(loc.getX() - player.getLocation().getX(), 2) + Math.pow(loc.getZ() - player.getLocation().getZ(), 2)); if (distance < 2.5D) { goal.setY(goal.getY() + player.getLocation().getY() - loc.getY()); @@ -57,9 +71,9 @@ public Location move(Player player, Location loc) { public Location idle(Player player, Location loc) { //Hover - if (hoverHeight >= hoverMaxHeightCap) + if (hoverHeight >= hoverHeightCeiling) hoverVelocity = -hoverSpeed; - if (hoverHeight <= hoverMinHeightCap) + if (hoverHeight <= hoverHeightFloor) hoverVelocity = hoverSpeed; Location newLoc = loc.clone(); hoverHeight += hoverVelocity; diff --git a/src/main/java/us/mytheria/bloblib/displayentity/DisplayFloatingPet.java b/src/main/java/us/mytheria/bloblib/displayentity/DisplayFloatingPet.java new file mode 100644 index 00000000..80b43d5e --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/displayentity/DisplayFloatingPet.java @@ -0,0 +1,319 @@ +package us.mytheria.bloblib.displayentity; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.entity.Display; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitTask; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import us.mytheria.bloblib.reflection.BlobReflectionLib; +import us.mytheria.bloblib.reflection.nonlivingentity.NonLivingEntityWrapper; + +import java.util.Objects; +import java.util.Random; +import java.util.UUID; + +public abstract class DisplayFloatingPet + implements DisplayPet { + private Particle particle; + protected T entity, vehicle; + private Location location; + private UUID owner; + private boolean activated, pauseLogic; + private String customName; + private SyncDisplayEntityAnimations animations; + private BukkitTask logicTask; + protected R display; + private final NonLivingEntityWrapper nleWrapper; + private final DisplayFloatingPetSettings settings; + + /** + * Creates a pet + * + * @param owner - the FloatingPet owner + * @param display - the display (like BlockData/ItemStack) + * (must be an item or a block) + * @param particle - the Particle to itemStack + * @param customName - the CustomName of the pet + * (if null will be used 'owner's Pet') + */ + public DisplayFloatingPet(@NotNull Player owner, + @NotNull R display, + @Nullable Particle particle, + @Nullable String customName, + @NotNull DisplayFloatingPetSettings settings + ) { + this.pauseLogic = false; + this.settings = Objects.requireNonNull(settings); + this.nleWrapper = BlobReflectionLib.getInstance() + .getNonLivingEntityWrapper(); + setOwner(owner.getUniqueId()); + setDisplay(Objects.requireNonNull(display)); + setCustomName(customName); + setParticle(particle); + } + + /** + * Spawns the pet. + * NEEDS TO BE CALLED SYNCHRONOUSLY! + */ + public void spawn() { + setActive(true); + Location loc = findOwnerOrFail().getLocation().clone(); + Random random = new Random(); + loc.setX(loc.getX() + random.nextInt(3) - 1); + loc.setZ(loc.getZ() + random.nextInt(3) - 1); + loc.setY(loc.getY() + 0.85); + setLocation(loc); + spawnEntity(loc); + entity.setTransformation(settings.displayMeasures().toTransformation()); + } + + /** + * Spawns the entity in world and initializes animations + * + * @param location - the location to spawn + */ + abstract void spawnEntity(Location location); + + /** + * Sets the pet display + * + * @param display - the new display + */ + public abstract void setDisplay(R display); + + /** + * Will set pet's custom name. + * If passing null, will be used 'owner's Pet' + * + * @param customName - the custom name + */ + public void setCustomName(String customName) { + this.customName = customName; + if (entity == null) + return; + entity.setCustomNameVisible(true); + entity.setCustomName(customName); + } + + protected void initAnimations(JavaPlugin plugin) { + animations = new SyncDisplayEntityAnimations(this, + settings.animationsCarrier()); + initLogic(plugin); + } + + private void initLogic(JavaPlugin plugin) { + EntityAnimationsCarrier animationsCarrier = settings.animationsCarrier(); + logicTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> { + Player owner = findOwner(); + if (owner == null || !owner.isOnline()) { + destroy(); + return; + } + spawnParticles(animationsCarrier.particlesOffset(), 0); + if (!isPauseLogic()) { + double distance = Math.sqrt(Math.pow(getLocation().getX() - owner.getLocation().getX(), 2) + Math.pow(getLocation().getZ() - owner.getLocation().getZ(), 2)); + if (distance >= animationsCarrier.teleportDistanceThreshold()) + teleport(owner.getLocation()); + else if (distance >= animationsCarrier.approachDistanceThreshold() || Math.abs(owner.getLocation().getY() + animationsCarrier.yOffset() - getLocation().getY()) > 1D) + move(); + else if (distance <= animationsCarrier.minimumDistance()) { + moveAway(); + } else { + idle(); + } + } + }, 0, 1); + } + + private void moveAway() { + this.location = animations.moveAway(findOwnerOrFail(), location); + } + + private void move() { + this.location = animations.move(findOwnerOrFail(), location); + } + + private void idle() { + this.location = animations.idle(findOwnerOrFail(), location); + } + + /** + * Similar to Entity#remove. + * Will mark the pet as deactivated and will remove the block display. + */ + public void destroy() { + setActive(false); + Bukkit.getScheduler().cancelTask(logicTask.getTaskId()); + if (entity != null) { + entity.remove(); + entity = null; + } + } + + /** + * Returns the pet display + * + * @return the display + */ + public R getDisplay() { + return display; + } + + /** + * Returns the owner of the pet + * + * @return the owner + */ + public UUID getOwner() { + return owner; + } + + /** + * Sets the owner of the pet + * + * @param owner - the new owner + */ + public void setOwner(UUID owner) { + this.owner = Objects.requireNonNull(owner); + } + + /** + * Returns the display entity of the pet + * + * @return the display entity + */ + @Override + public T getEntity() { + return entity; + } + + /** + * Retrieves pet's name. + * + * @return if customName is null, returns 'owner's Pet', else returns customName + */ + public String getCustomName() { + String name = findOwnerOrFail().getName() + "'s Pet"; + if (this.customName != null) + name = this.customName; + return name; + } + + /** + * Teleports the pet + * + * @param loc the new location + */ + public void teleport(Location loc) { + if (vehicle != null) { + loadChunks(location); + loadChunks(loc); + nleWrapper.vehicleTeleport(vehicle, loc); + } else + throw new NullPointerException("Expected vehicle is null"); + setLocation(loc); + } + + private void loadChunks(Location loc) { + if (!loc.getChunk().isLoaded()) + loc.getChunk().load(); + + if (!loc.getWorld().getChunkAt(loc.getChunk().getX() + 1, loc.getChunk().getZ()).isLoaded()) + loc.getWorld().getChunkAt(loc.getChunk().getX() + 1, loc.getChunk().getZ()).load(); + + if (!loc.getWorld().getChunkAt(loc.getChunk().getX() - 1, loc.getChunk().getZ()).isLoaded()) + loc.getWorld().getChunkAt(loc.getChunk().getX() - 1, loc.getChunk().getZ()).load(); + + if (!loc.getWorld().getChunkAt(loc.getChunk().getX(), loc.getChunk().getZ() + 1).isLoaded()) + loc.getWorld().getChunkAt(loc.getChunk().getX(), loc.getChunk().getZ() + 1).load(); + + if (!loc.getWorld().getChunkAt(loc.getChunk().getX(), loc.getChunk().getZ() - 1).isLoaded()) + loc.getWorld().getChunkAt(loc.getChunk().getX(), loc.getChunk().getZ() - 1).load(); + + if (!loc.getWorld().getChunkAt(loc.getChunk().getX() + 1, loc.getChunk().getZ() + 1).isLoaded()) + loc.getWorld().getChunkAt(loc.getChunk().getX() + 1, loc.getChunk().getZ() + 1).load(); + + if (!loc.getWorld().getChunkAt(loc.getChunk().getX() - 1, loc.getChunk().getZ() - 1).isLoaded()) + loc.getWorld().getChunkAt(loc.getChunk().getX() - 1, loc.getChunk().getZ() - 1).load(); + + if (!loc.getWorld().getChunkAt(loc.getChunk().getX() - 1, loc.getChunk().getZ() + 1).isLoaded()) + loc.getWorld().getChunkAt(loc.getChunk().getX() - 1, loc.getChunk().getZ() + 1).load(); + + if (!loc.getWorld().getChunkAt(loc.getChunk().getX() + 1, loc.getChunk().getZ() - 1).isLoaded()) + loc.getWorld().getChunkAt(loc.getChunk().getX() + 1, loc.getChunk().getZ() - 1).load(); + + } + + /** + * Returns the location of the pet + * + * @return the location + */ + public Location getLocation() { + return location; + } + + private void setLocation(Location location) { + this.location = location; + } + + /** + * Returns if the pet is activated. + * If true, it means that it is spawned. + * If false, it means that it was called + * to be removed. + * + * @return if the pet is activated + */ + public boolean isActive() { + return activated; + } + + private void setActive(boolean activated) { + this.activated = activated; + } + + /** + * Returns if the pet logic is paused + * + * @return if the logic is paused + */ + public boolean isPauseLogic() { + return pauseLogic; + } + + /** + * Sets if the pet logic should be paused + * + * @param pauseLogic - if the logic should be paused + * if true, will make pet static/paused + */ + public void setPauseLogic(boolean pauseLogic) { + this.pauseLogic = pauseLogic; + } + + /** + * Returns the particle of the pet + * + * @return the particle + */ + @Nullable + public Particle getParticle() { + return particle; + } + + /** + * Sets the particle of the pet + * + * @param particle - the new particle + * if null, the pet will not have a particle + */ + public void setParticle(Particle particle) { + this.particle = particle; + } +} \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/displayentity/DisplayFloatingPetSettings.java b/src/main/java/us/mytheria/bloblib/displayentity/DisplayFloatingPetSettings.java new file mode 100644 index 00000000..22d293c3 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/displayentity/DisplayFloatingPetSettings.java @@ -0,0 +1,69 @@ +package us.mytheria.bloblib.displayentity; + +import org.bukkit.configuration.ConfigurationSection; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public record DisplayFloatingPetSettings(EntityAnimationsCarrier animationsCarrier, + DisplayMeasures displayMeasures) { + private static final DisplayFloatingPetSettings DEFAULT = new DisplayFloatingPetSettings( + EntityAnimationsCarrier.DEFAULT(), DisplayMeasures.DEFAULT()); + + public static DisplayFloatingPetSettings DEFAULT() { + return DEFAULT; + } + + /** + * Will read from a ConfigurationSection. + * + * @param section The ConfigurationSection to read from. + * @return The DisplayFloatingPetSettings read from the ConfigurationSection. + */ + @NotNull + public static DisplayFloatingPetSettings READ_OR_FAIL_FAST(ConfigurationSection section) { + EntityAnimationsCarrier animationsCarrier; + ConfigurationSection animations = section.getConfigurationSection("Animations"); + if (animations == null) + animationsCarrier = EntityAnimationsCarrier.DEFAULT(); + else + animationsCarrier = EntityAnimationsCarrier.READ_OR_FAIL_FAST(animations); + DisplayMeasures displayMeasures; + ConfigurationSection measurements = section.getConfigurationSection("Measurements"); + if (measurements == null) + displayMeasures = DisplayMeasures.DEFAULT(); + else + displayMeasures = DisplayMeasures.READ_OR_FAIL_FAST(measurements); + return new DisplayFloatingPetSettings(animationsCarrier, displayMeasures); + } + + /** + * Will read from a ConfigurationSection. If any exception is found, will + * return null. + * + * @param section The ConfigurationSection to read from. + * @return The DisplayFloatingPetSettings read from the ConfigurationSection. + * Null if any exception is found. + */ + @Nullable + public static DisplayFloatingPetSettings READ_OR_NULL(ConfigurationSection section) { + DisplayFloatingPetSettings settings; + try { + settings = READ_OR_FAIL_FAST(section); + return settings; + } catch (Exception e) { + return null; + } + } + + /** + * Will serialize to a ConfigurationSection. + * + * @param section The ConfigurationSection to serialize to. + * @param name The name of the ConfigurationSection to serialize to. + */ + public void serialize(ConfigurationSection section, String name) { + ConfigurationSection settings = section.createSection(name); + animationsCarrier.serialize(settings, "Animations"); + displayMeasures.serialize(settings, "Measurements"); + } +} diff --git a/src/main/java/us/mytheria/bloblib/displayentity/DisplayMeasures.java b/src/main/java/us/mytheria/bloblib/displayentity/DisplayMeasures.java new file mode 100644 index 00000000..6bb6f459 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/displayentity/DisplayMeasures.java @@ -0,0 +1,131 @@ +package us.mytheria.bloblib.displayentity; + +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.util.Transformation; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +/** + * The idea behind DisplayMeasures is being able to resize Display entities + * while automatically adjusting their translation so the Display entity + * is centered. + * + * @param scaleX The scale on the X axis. + * @param scaleY The scale on the Y axis. + * @param scaleZ The scale on the Z axis. + * @param yOffset The Y offset. + */ +public record DisplayMeasures(float scaleX, float scaleY, float scaleZ, + float yOffset) { + private static final DisplayMeasures DEFAULT_MEASURES = + new DisplayMeasures(1, 1, 1, 0); + + /** + * Will return a new DisplayMeasures with the given values + * with no Y-Offset. + * + * @param scaleX The scale on the X axis. + * @param scaleY The scale on the Y axis. + * @param scaleZ The scale on the Z axis. + * @return A new DisplayMeasures with the given values. + */ + public static DisplayMeasures of(float scaleX, float scaleY, float scaleZ) { + return new DisplayMeasures(scaleX, scaleY, scaleZ, 0); + } + + /** + * Will return the default DisplayMeasures. + * Scale-X: 1.0 + * Scale-Y: 1.0 + * Scale-Z: 1.0 + * Y-Offset: 0.0 + * + * @return The default DisplayMeasures. + */ + public static DisplayMeasures DEFAULT() { + return DEFAULT_MEASURES; + } + + /** + * Will read the DisplayMeasures from the given ConfigurationSection. + * + * @param section The ConfigurationSection to read from. + * @return The DisplayMeasures read from the ConfigurationSection. + */ + @NotNull + public static DisplayMeasures READ_OR_FAIL_FAST(ConfigurationSection section) { + return new DisplayMeasures((float) section.getDouble("Scale-X", 1), + (float) section.getDouble("Scale-Y", 1), + (float) section.getDouble("Scale-Z", 1), + (float) section.getDouble("Y-Offset", 0)); + } + + /** + * Will read the DisplayMeasures from the given ConfigurationSection. + * If any exception is thrown, null will be returned. + * + * @param section The ConfigurationSection to read from. + * @return The DisplayMeasures read from the ConfigurationSection, + * null if any exception is thrown. + */ + @Nullable + public static DisplayMeasures READ_OR_NULL(ConfigurationSection section) { + DisplayMeasures displayMeasures; + try { + displayMeasures = READ_OR_FAIL_FAST(section); + return displayMeasures; + } catch (Exception e) { + return null; + } + } + + public Vector3f getScale() { + return new Vector3f(scaleX, scaleY, scaleZ); + } + + public Vector3f getTranslation() { + return new Vector3f(0, yOffset, 0); + } + + /** + * Will merge the given Transformation with this DisplayMeasures. + * This allows using existing LeftRotation & RightRotation Quaternions + * + * @param transformation The Transformation to merge with. + * @return A new Transformation with the merged values. + */ + public Transformation merge(Transformation transformation) { + return new Transformation(getTranslation(), + transformation.getLeftRotation(), + getScale(), + transformation.getRightRotation()); + } + + /** + * Will return a new Transformation with the values of this DisplayMeasures. + * + * @return A new Transformation with the values of this DisplayMeasures. + */ + public Transformation toTransformation() { + return new Transformation(getTranslation(), + new Quaternionf(0, 0, 0, 1), + getScale(), + new Quaternionf(0, 0, 0, 1)); + } + + /** + * Will serialize this DisplayMeasures to the given ConfigurationSection. + * + * @param section The ConfigurationSection to serialize to. + * @param name The name of the ConfigurationSection to serialize to. + */ + public void serialize(ConfigurationSection section, String name) { + ConfigurationSection measurements = section.createSection(name); + measurements.set("Scale-X", scaleX); + measurements.set("Scale-Y", scaleY); + measurements.set("Scale-Z", scaleZ); + measurements.set("Y-Offset", yOffset); + } +} diff --git a/src/main/java/us/mytheria/bloblib/displayentity/DisplayPet.java b/src/main/java/us/mytheria/bloblib/displayentity/DisplayPet.java new file mode 100644 index 00000000..1674146b --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/displayentity/DisplayPet.java @@ -0,0 +1,56 @@ +package us.mytheria.bloblib.displayentity; + +import org.bukkit.Location; +import org.bukkit.Nameable; +import org.bukkit.Particle; +import org.bukkit.entity.Entity; +import org.jetbrains.annotations.Nullable; +import us.mytheria.bloblib.entities.Ownable; +import us.mytheria.bloblib.entities.ParticleContainer; + +public interface DisplayPet + extends DisplayEntity, + Nameable, ParticleContainer, Ownable { + + /** + * Retrieves the current particle that FloatingPet emits. + * If null, no particle is emitted. + * + * @return The current particle, or null if no particle is set. + */ + @Nullable + Particle getParticle(); + + /** + * Sets the particle that FloatingPet emits. + * If particle is null, no particle must be emitted. + * + * @param particle The particle to be set. + */ + void setParticle(Particle particle); + + /** + * Will spawn particles at FloatingPet's location + * if FloatingPet#getParticle() is not null. + * Passes '0.7' offsetY and '0' count to spawnParticles(double, int). + */ + default void spawnParticles() { + spawnParticles(0.7, 0); + } + + /** + * Will spawn particles at FloatingPet's location + * if FloatingPet#getParticle() is not null. + * + * @param offsetY The offset on the Y axis to spawn the particles. + * @param count The amount of particles to spawn. + */ + default void spawnParticles(double offsetY, int count) { + Particle particle = getParticle(); + if (particle == null) + return; + Location location = getLocation().clone(); + location.setY(location.getY() + offsetY); + location.getWorld().spawnParticle(particle, location, count); + } +} diff --git a/src/main/java/us/mytheria/bloblib/displayentity/DisplayPetData.java b/src/main/java/us/mytheria/bloblib/displayentity/DisplayPetData.java new file mode 100644 index 00000000..019ccb72 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/displayentity/DisplayPetData.java @@ -0,0 +1,173 @@ +package us.mytheria.bloblib.displayentity; + +import org.bukkit.Particle; +import org.bukkit.block.data.BlockData; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; + +public class DisplayPetData { + @Nullable + private final ItemStack itemStack; + @Nullable + private final BlockData blockData; + @Nullable + private final Particle particle; + @Nullable + private final String customName; + + /** + * Will hold all attributes for a FloatingPet + * except the owner themselves. The idea behind + * it is that FloatingPetData could be stored + * in a manager and later used to make new instances + * of FloatingPets. + * From now on, you will be required to pass + * a FloatingPetRecord to the FloatingPetData + * in all minor classes. + * + * @param record the FloatingPetRecord to pass from + */ + public DisplayPetData(DisplayPetRecord record) { + this.itemStack = record.itemStack(); + this.blockData = record.blockData(); + this.particle = record.particle(); + this.customName = record.customName(); + } + + /** + * Will create a new instance of ArmorStandFloatingPet + * + * @param owner the owner of the pet + * @return a new instance of ArmorStandFloatingPet + */ + public ArmorStandFloatingPet asArmorStand(Player owner) { + if (itemStack == null || itemStack.getType().isAir()) + throw new IllegalStateException("ItemStack cannot be null nor be air"); + return new ArmorStandFloatingPet(owner, itemStack, particle, customName, + EntityAnimationsCarrier.DEFAULT()); + } + + /** + * Will create a new instance of ItemDisplayFloatingPet + * + * @param owner the owner of the pet + * @return a new instance of ItemDisplayFloatingPet + */ + public ItemDisplayFloatingPet asItemDisplay(Player owner) { + if (itemStack == null || itemStack.getType().isAir()) + throw new IllegalStateException("ItemStack cannot be null nor be air"); + return new ItemDisplayFloatingPet(owner, itemStack, particle, customName, + DisplayFloatingPetSettings.DEFAULT()); + } + + /** + * Will create a new instance of ArmorStandFloatingPet + * and parse the placeholder with the owner's name. + * Example: + * FloatingPetData test = new FloatingPetData(new ItemStack(Material.DIAMOND), + * Particle.FLAME, "%owner%'s Pet"); + * Player player = Bukkit.getPlayer("Notch"); + * ArmorStandFloatingPet floatingPet = test.asArmorStandAndParsePlaceHolder(player, "%owner%"); + * assert floatingPet.getCustomName().equals("Notch's Pet"); + * // true + * + * @param owner the owner of the pet + * @param ownerPlaceholder the placeholder to replace with the owner's name + * @return a new instance of ArmorStandFloatingPet + */ + public ArmorStandFloatingPet asArmorStandAndParsePlaceHolder(Player owner, String ownerPlaceholder) { + ArmorStandFloatingPet floatingPet = asArmorStand(owner); + floatingPet.setCustomName(floatingPet.getCustomName().replace(ownerPlaceholder, owner.getName())); + return floatingPet; + } + + /** + * Will create a new instance of ItemDisplayFloatingPet + * and parse the placeholder with the owner's name. + * Example: + * FloatingPetData test = new FloatingPetData(new ItemStack(Material.DIAMOND), + * Particle.FLAME, "%owner%'s Pet"); + * Player player = Bukkit.getPlayer("Notch"); + * ItemDisplayFloatingPet floatingPet = test.asItemDisplayAndParsePlaceHolder(player, "%owner%"); + * assert floatingPet.getCustomName().equals("Notch's Pet"); + * // true + * + * @param owner the owner of the pet + * @param ownerPlaceholder the placeholder to replace with the owner's name + * @return a new instance of ItemDisplayFloatingPet + */ + public ItemDisplayFloatingPet asItemDisplayAndParsePlaceHolder(Player owner, String ownerPlaceholder) { + ItemDisplayFloatingPet floatingPet = asItemDisplay(owner); + floatingPet.setCustomName(floatingPet.getCustomName().replace(ownerPlaceholder, owner.getName())); + return floatingPet; + } + + /** + * Will get the ItemStack of the FloatingPetData + * if available. Null otherwise. + * + * @return the ItemStack of the FloatingPetData, + * null otherwise + */ + @Nullable + public ItemStack getItemStack() { + return itemStack; + } + + /** + * Will get the BlockData of the FloatingPetData + * if available. Null otherwise. + * + * @return the BlockData of the FloatingPetData, + * null otherwise + */ + @Nullable + public BlockData getBlockData() { + return blockData; + } + + /** + * Will get the Particle of the FloatingPetData + * + * @return the Particle of the FloatingPetData + */ + @Nullable + public Particle getParticle() { + return particle; + } + + /** + * Will get the custom name of the FloatingPetData + * + * @return the custom name of the FloatingPetData + */ + @Nullable + public String getCustomName() { + return customName; + } + + /** + * Will serialize the FloatingPetData in a configuration section + * under the patch name. + * Example: + * ConfigurationSection section = YamlConfiguration.loadConfiguration(file); + * FloatingPetData data = new FloatingPetData(new ItemStack(Material.DIAMOND), + * Particle.FLAME, "%owner%'s Pet"); + * data.serialize(section, "FloatingPetRecord"); + * // YAML file would look like: + * // FloatingPetRecord: + * // ItemStack: + * // #blablabla + * // Particle: FLAME + * // CustomName: "%owner%'s Pet" + * + * @param configurationSection the configuration section to serialize in + * @param path the path to serialize in + */ + public void serialize(ConfigurationSection configurationSection, String path) { + DisplayPetRecord record = new DisplayPetRecord(itemStack, blockData, particle, customName); + record.serialize(configurationSection, path); + } +} diff --git a/src/main/java/us/mytheria/bloblib/floatingpet/FloatingPetManager.java b/src/main/java/us/mytheria/bloblib/displayentity/DisplayPetManager.java similarity index 88% rename from src/main/java/us/mytheria/bloblib/floatingpet/FloatingPetManager.java rename to src/main/java/us/mytheria/bloblib/displayentity/DisplayPetManager.java index e33ea4fe..609cd7e9 100644 --- a/src/main/java/us/mytheria/bloblib/floatingpet/FloatingPetManager.java +++ b/src/main/java/us/mytheria/bloblib/displayentity/DisplayPetManager.java @@ -1,4 +1,4 @@ -package us.mytheria.bloblib.floatingpet; +package us.mytheria.bloblib.displayentity; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -19,7 +19,7 @@ import java.util.*; import java.util.function.Function; -public class FloatingPetManager extends Manager implements Listener { +public class DisplayPetManager> extends Manager implements Listener { private Map> ownerMap; private Map petMap; @Nullable @@ -38,16 +38,17 @@ public class FloatingPetManager extends Manager implement * If null, no event will be called. * Event CANNOT implement Cancelable! */ - public FloatingPetManager(ManagerDirector managerDirector, @Nullable Function destroyEvent) { + public DisplayPetManager(ManagerDirector managerDirector, @Nullable Function destroyEvent) { super(managerDirector); - ownerMap = new HashMap<>(); - petMap = new HashMap<>(); + reload(); this.destroyEvent = destroyEvent; Bukkit.getPluginManager().registerEvents(this, managerDirector.getPlugin()); } @Override public void unload() { + if (ownerMap == null) + return; for (List t : ownerMap.values()) { for (T floatingPet : t) { floatingPet.destroy(); @@ -55,8 +56,13 @@ public void unload() { } } + /** + * Will destroy all current loaded pets and clean + * the memory. + */ @Override public void reload() { + unload(); ownerMap = new HashMap<>(); petMap = new HashMap<>(); } @@ -68,8 +74,8 @@ public void reload() { * @param floatingPet - the pet to add */ public void addPet(T floatingPet) { - petMap.put(floatingPet.getArmorStand().getUniqueId(), floatingPet); - ownerMap.computeIfAbsent(floatingPet.getOwner().getUniqueId(), k -> new ArrayList<>()).add(floatingPet); + petMap.put(floatingPet.getEntity().getUniqueId(), floatingPet); + ownerMap.computeIfAbsent(floatingPet.getOwner(), k -> new ArrayList<>()).add(floatingPet); } /** @@ -154,7 +160,7 @@ public void onWorldChange(PlayerChangedWorldEvent e) { public void onQuit(PlayerQuitEvent e) { Player player = e.getPlayer(); hasPets(player).ifPresent(pets -> pets.forEach(pet -> { - petMap.remove(pet.getArmorStand().getUniqueId()); + petMap.remove(pet.getEntity().getUniqueId()); if (destroyEvent != null) { Event event = destroyEvent.apply(pet); Bukkit.getPluginManager().callEvent(event); diff --git a/src/main/java/us/mytheria/bloblib/displayentity/DisplayPetRecord.java b/src/main/java/us/mytheria/bloblib/displayentity/DisplayPetRecord.java new file mode 100644 index 00000000..0a1533fd --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/displayentity/DisplayPetRecord.java @@ -0,0 +1,63 @@ +package us.mytheria.bloblib.displayentity; + +import org.bukkit.Bukkit; +import org.bukkit.Particle; +import org.bukkit.block.data.BlockData; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.inventory.ItemStack; +import us.mytheria.bloblib.BlobLib; +import us.mytheria.bloblib.itemstack.ItemStackBuilder; +import us.mytheria.bloblib.itemstack.ItemStackReader; +import us.mytheria.bloblib.utilities.SerializationLib; +import us.mytheria.bloblib.utilities.TextColor; + +/** + * A data carrier for the basic FloatingPet data. + * The idea for a more complex data carrier is that + * you extend FloatingPetData and implement your + * own FloatingPetReader which will inject into + * the FloatingPetData. + * + * @param itemStack the ItemStack to display + * @param particle the particle to display + * @param customName the custom name to display + */ +public record DisplayPetRecord(ItemStack itemStack, BlockData blockData, Particle particle, String customName) { + + public static DisplayPetRecord read(ConfigurationSection section) { + ItemStack itemStack = null; + if (section.isConfigurationSection("ItemStack")) { + ItemStackBuilder builder = ItemStackReader + .READ_OR_FAIL_FAST(section.getConfigurationSection("ItemStack")); + itemStack = builder.build(); + } + BlockData blockData = null; + if (section.isString("BlockData")) { + blockData = Bukkit.createBlockData(section.getString("BlockData")); + } + Particle particle = null; + if (section.contains("Particle")) + particle = SerializationLib.deserializeParticle(section.getString("Particle")); + String customName = null; + if (section.contains("CustomName")) + customName = TextColor.PARSE(section.getString("CustomName")); + if (itemStack == null && blockData == null) { + BlobLib.getAnjoLogger().singleError("ItemStack and BlockData are both empty at " + section.getCurrentPath()); + return null; + } + return new DisplayPetRecord(itemStack, blockData, particle, customName); + } + + public void serialize(ConfigurationSection configurationSection, String path) { + if (itemStack == null && blockData == null) + throw new IllegalArgumentException("ItemStack and BlockData cannot both be null"); + if (itemStack != null) + configurationSection.set(path + ".ItemStack", itemStack); + if (blockData != null) + configurationSection.set(path + ".BlockData", blockData.getAsString(true)); + if (particle != null) + configurationSection.set(path + ".Particle", particle); + if (customName != null) + configurationSection.set(path + ".CustomName", customName); + } +} diff --git a/src/main/java/us/mytheria/bloblib/displayentity/EntityAnimationsCarrier.java b/src/main/java/us/mytheria/bloblib/displayentity/EntityAnimationsCarrier.java new file mode 100644 index 00000000..682cdcec --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/displayentity/EntityAnimationsCarrier.java @@ -0,0 +1,102 @@ +package us.mytheria.bloblib.displayentity; + +import org.bukkit.configuration.ConfigurationSection; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public record EntityAnimationsCarrier(double followSpeed, + double walkAwaySpeed, + double hoverSpeed, + double hoverHeightCeiling, + double hoverHeightFloor, + double yOffset, + double particlesOffset, + double teleportDistanceThreshold, + double approachDistanceThreshold, + double minimumDistance) { + private static final EntityAnimationsCarrier DEFAULT_ANIMATIONS_CARRIER = new EntityAnimationsCarrier( + 0.35, + 0.2, + 0.03, + 0.2, + -0.3, + 1.75, + -1.25, + 5.0, + 2.5, + 1.0 + ); + + /** + * Will return the default EntityAnimationsCarrier. + * Follow-Speed: 0.225 + * Walk-Away-Speed: 0.2 + * Hover-Speed: 0.03 + * Hover-Height-Ceiling: 0.2 + * Hover-Height-Floor: -0.3 + * Y-Offset: 1.5 + * Particles-Offset: -1.25 + * Teleport-Distance-Threshold: 5.0 + * Approach-Distance-Threshold: 2.5 + * Minimum-Distance: 1.0 + * + * @return The default EntityAnimationsCarrier. + */ + public static EntityAnimationsCarrier DEFAULT() { + return DEFAULT_ANIMATIONS_CARRIER; + } + + /** + * Will read from a ConfigurationSection. + * + * @param section The ConfigurationSection to read from. + * @return The EntityAnimationsCarrier read from the ConfigurationSection. + */ + @NotNull + public static EntityAnimationsCarrier READ_OR_FAIL_FAST(ConfigurationSection section) { + return new EntityAnimationsCarrier( + section.getDouble("Follow-Speed", 0.225), + section.getDouble("Walk-Away-Speed", 0.2), + section.getDouble("Hover-Speed", 0.03), + section.getDouble("Hover-Height-Ceiling", 0.2), + section.getDouble("Hover-Height-Floor", -0.3), + section.getDouble("Y-Offset", 1.5), + section.getDouble("Particles-Offset", -1.25), + section.getDouble("Teleport-Distance-Threshold", 5.0), + section.getDouble("Approach-Distance-Threshold", 2.5), + section.getDouble("Minimum-Distance", 1.0)); + } + + /** + * Will read from a ConfigurationSection. If any exception is found, will + * return null. + * + * @param section The ConfigurationSection to read from. + * @return The EntityAnimationsCarrier read from the ConfigurationSection. + * Null if any exception is found. + */ + @Nullable + public static EntityAnimationsCarrier READ_OR_NULL(ConfigurationSection section) { + EntityAnimationsCarrier carrier; + try { + carrier = READ_OR_FAIL_FAST(section); + return carrier; + } catch (Exception e) { + return null; + } + } + + /** + * Serializes the EntityAnimationsCarrier to a ConfigurationSection. + * + * @param section The ConfigurationSection to serialize to. + */ + public void serialize(ConfigurationSection section, String name) { + ConfigurationSection animations = section.createSection(name); + animations.set("Hover-Speed", hoverSpeed); + animations.set("Hover-Height-Ceiling", hoverHeightCeiling); + animations.set("Hover-Height-Floor", hoverHeightFloor); + animations.set("Y-Offset", yOffset); + animations.set("Particles-Offset", particlesOffset); + } +} diff --git a/src/main/java/us/mytheria/bloblib/displayentity/ItemDisplayFloatingPet.java b/src/main/java/us/mytheria/bloblib/displayentity/ItemDisplayFloatingPet.java new file mode 100644 index 00000000..2da0bda6 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/displayentity/ItemDisplayFloatingPet.java @@ -0,0 +1,55 @@ +package us.mytheria.bloblib.displayentity; + +import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.ItemDisplay; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Transformation; +import org.jetbrains.annotations.Nullable; +import us.mytheria.bloblib.BlobLib; + +public class ItemDisplayFloatingPet extends DisplayFloatingPet { + /** + * Creates a pet + * + * @param owner - the FloatingPet owner + * @param display - the display (like BlockData/ItemStack) + * (must be an item or a block) + * @param particle - the Particle to itemStack + * @param customName - the CustomName of the pet + * (if null will be used 'owner's Pet') + */ + public ItemDisplayFloatingPet(Player owner, ItemStack display, + @Nullable Particle particle, + @Nullable String customName, + DisplayFloatingPetSettings settings) { + super(owner, display, particle, customName, settings); + } + + void spawnEntity(Location location) { + BlobLib plugin = BlobLib.getInstance(); + entity = (ItemDisplay) location.getWorld().spawnEntity(location, + EntityType.ITEM_DISPLAY); + vehicle = (ItemDisplay) location.getWorld().spawnEntity(location, + EntityType.ITEM_DISPLAY); + entity.setPersistent(false); + vehicle.setPersistent(false); + if (!vehicle.addPassenger(entity)) { + throw new RuntimeException("Failed to add passenger to vehicle"); + } + setCustomName(getCustomName()); + entity.setItemStack(getDisplay()); + Transformation transformation = entity.getTransformation(); + entity.setTransformation(new Transformation( + transformation.getTranslation().add(0f, -0.5f, 0f), + transformation.getLeftRotation(), transformation.getScale(), + transformation.getRightRotation())); + initAnimations(plugin); + } + + public void setDisplay(ItemStack display) { + this.display = display; + } +} diff --git a/src/main/java/us/mytheria/bloblib/displayentity/SyncDisplayEntityAnimations.java b/src/main/java/us/mytheria/bloblib/displayentity/SyncDisplayEntityAnimations.java new file mode 100644 index 00000000..e4be2935 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/displayentity/SyncDisplayEntityAnimations.java @@ -0,0 +1,117 @@ +package us.mytheria.bloblib.displayentity; + +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; + +public class SyncDisplayEntityAnimations { + + private final double followSpeed, walkAwaySpeed, hoverSpeed, hoverHeightCeiling, + hoverHeightFloor, yOffset; + private double hoverVelocity, hoverHeight; + private final DisplayEntity pet; + + public SyncDisplayEntityAnimations(DisplayEntity pet, + double followSpeed, + double walkAwaySpeed, + double hoverSpeed, + double hoverHeightCeiling, + double hoverHeightFloor, + double yOffset) { + this.pet = pet; + this.followSpeed = followSpeed; + this.walkAwaySpeed = walkAwaySpeed; + this.hoverSpeed = hoverSpeed; + this.hoverHeightCeiling = hoverHeightCeiling; + this.hoverHeightFloor = hoverHeightFloor; + this.hoverVelocity = hoverSpeed; + this.hoverHeight = 0; + this.yOffset = yOffset; + } + + public SyncDisplayEntityAnimations(DisplayEntity pet, + EntityAnimationsCarrier carrier) { + this(pet, carrier.followSpeed(), carrier.walkAwaySpeed(), carrier.hoverSpeed(), + carrier.hoverHeightCeiling(), carrier.hoverHeightFloor(), + carrier.yOffset()); + } + + public Location move(Player player, Location loc) { + Vector goal = vectorFromLocation(player.getLocation()); + goal.setY(goal.getY() + yOffset); + double distance = Math.sqrt(Math.pow(loc.getX() - player.getLocation().getX(), 2) + Math.pow(loc.getZ() - player.getLocation().getZ(), 2)); + if (distance < 2.5D) { + goal.setY(goal.getY() + player.getLocation().getY() - loc.getY()); + goal.setX(loc.getX()); + goal.setZ(loc.getZ()); + } + Vector start = vectorFromLocation(loc); + Vector direction = normalize(goal.subtract(start)); + Location newLoc = loc.clone(); + newLoc.add(direction.multiply(followSpeed)); + //Rotation +// double a = player.getLocation().getX() - newLoc.getX(); +// double b = player.getLocation().getZ() - newLoc.getZ(); +// double angle = Math.atan(b / a); +// angle = angle * (180 / Math.PI); +// if (player.getLocation().getX() - newLoc.getX() >= 0) { +// angle += 180; +// } +// angle += 90; +// newLoc.setYaw((float) angle); + pet.teleport(newLoc); + return newLoc; + } + + public Location idle(Player player, Location loc) { + //Hover + if (hoverHeight >= hoverHeightCeiling) + hoverVelocity = -hoverSpeed; + if (hoverHeight <= hoverHeightFloor) + hoverVelocity = hoverSpeed; + Location newLoc = loc.clone(); + hoverHeight += hoverVelocity; + newLoc.setY(newLoc.getY() + hoverVelocity); + //Rotation + double a = player.getLocation().getX() - newLoc.getX(); + double b = player.getLocation().getZ() - newLoc.getZ(); + double angle = Math.atan(b / a); + angle = angle * (180 / Math.PI); + if (player.getLocation().getX() - newLoc.getX() >= 0) { + angle += 180; + } + angle += 90; + newLoc.setYaw((float) angle); + pet.teleport(newLoc); + return newLoc; + } + + public Location moveAway(Player player, Location loc) { + Vector goal = vectorFromLocation(player.getLocation()); + Vector start = vectorFromLocation(loc); + Vector direction = normalize(goal.subtract(start).multiply(-1)); + Location newLoc = loc.clone(); + newLoc.add(direction.multiply(walkAwaySpeed)); + newLoc.setY(loc.getY()); + //Rotation + double a = player.getLocation().getX() - newLoc.getX(); + double b = player.getLocation().getZ() - newLoc.getZ(); + double angle = Math.atan(b / a); + angle = angle * (180 / Math.PI); + if (player.getLocation().getX() - newLoc.getX() >= 0) { + angle += 180; + } + angle += 90; + newLoc.setYaw((float) angle); + pet.teleport(newLoc); + return newLoc; + } + + protected Vector vectorFromLocation(Location location) { + return location.toVector(); + } + + protected Vector normalize(Vector vec) { + return new Vector(vec.getX() / vec.length(), vec.getY() / vec.length(), vec.getZ() / vec.length()); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/ArrayNavigator.java b/src/main/java/us/mytheria/bloblib/entities/ArrayNavigator.java new file mode 100644 index 00000000..9db6e495 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/ArrayNavigator.java @@ -0,0 +1,33 @@ +package us.mytheria.bloblib.entities; + +public class ArrayNavigator { + private final T[] array; + private int index; + + public static ArrayNavigator ofBoolean() { + return new ArrayNavigator<>(new Boolean[]{false, true}); + } + + public static ArrayNavigator defaultTrueBoolean() { + return new ArrayNavigator<>(new Boolean[]{true, false}); + } + + public ArrayNavigator(T[] array) { + this.array = array; + this.index = 0; + } + + public T next() { + index = (index + 1) % array.length; + return array[index]; + } + + public T previous() { + index = (index - 1 + array.length) % array.length; + return array[index]; + } + + public T current() { + return array[index]; + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/BinarySerializable.java b/src/main/java/us/mytheria/bloblib/entities/BinarySerializable.java new file mode 100644 index 00000000..38b8b236 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/BinarySerializable.java @@ -0,0 +1,72 @@ +package us.mytheria.bloblib.entities; + +import org.bson.types.Binary; +import org.jetbrains.annotations.Nullable; + +/** + * This interface represents an object + * that holds transient attributes that + * are later serialized into a {@link BlobCrudable} + * whenever the object is saved. + */ +public interface BinarySerializable { + /** + * Each BlobSerialize should hold a BlobCrudable which + * will be used to serialize and deserialize + * all kind of attributes. + * This is due to allowing the BlobSerializable to be + * updated with new attributes in the future + * without having to change anything inside + * the database. + * + * @return the BlobCrudable + */ + BlobCrudable blobCrudable(); + + /** + * This method should write all the transient + * attributes to the BlobCrudable that are + * stored inside the BlobSerializable. + *

+ * Note, it needs to return the updated BlobCrudable + * so that it can be used in the next method. + * + * @return the updated BlobCrudable + */ + BlobCrudable serializeAllAttributes(); + + /** + * Will provide {@link BlobCrudable#getIdentification()} + * It may be a UUID or whatever the implementation decides. + * + * @return the identification + */ + default String getIdentification() { + return blobCrudable().getIdentification(); + } + + /** + * Will serialize a byte array into the BlobCrudable + * + * @param byteArray the byte array + * @param key the key + */ + default void serializeByteArray(byte[] byteArray, String key) { + blobCrudable().getDocument().put(key, new Binary(byteArray)); + } + + /** + * Will get a byte array from the BlobCrudable + * + * @param key the key + * @return the byte array + */ + default byte @Nullable [] getByteArray(String key) { + try { + Binary byteArray = (Binary) blobCrudable().getDocument().get(key); + return byteArray.getData(); + } catch (Exception e) { + return null; + } + } +} \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/entities/BlobCrudable.java b/src/main/java/us/mytheria/bloblib/entities/BlobCrudable.java index 07b383c1..5cf0b2f3 100644 --- a/src/main/java/us/mytheria/bloblib/entities/BlobCrudable.java +++ b/src/main/java/us/mytheria/bloblib/entities/BlobCrudable.java @@ -5,27 +5,22 @@ import java.util.List; import java.util.Optional; -import java.util.UUID; public final class BlobCrudable implements Crudable { - private final UUID id; + private final String id; private final Document document; - public BlobCrudable(UUID id, Document document) { + public BlobCrudable(String id, Document document) { this.id = id; this.document = document; } - public BlobCrudable(UUID uuid) { + public BlobCrudable(String uuid) { this(uuid, new Document()); } @Override public String getIdentification() { - return id.toString(); - } - - public UUID getUUID() { return id; } @@ -42,11 +37,10 @@ public Optional hasLong(String key) { } public Optional hasFloat(String key) { - Double value = document.getDouble(key); + Float value = document.get(key, Float.class); if (value == null) return Optional.empty(); - Float floatValue = value.floatValue(); - return Optional.ofNullable(floatValue); + return Optional.ofNullable(value); } public Optional hasDouble(String key) { @@ -58,19 +52,17 @@ public Optional hasInteger(String key) { } public Optional hasShort(String key) { - Integer value = document.getInteger(key); + Short value = document.get(key, Short.class); if (value == null) return Optional.empty(); - Short shortValue = value.shortValue(); - return Optional.ofNullable(shortValue); + return Optional.ofNullable(value); } public Optional hasByte(String key) { - Integer value = document.getInteger(key); + Byte value = document.get(key, Byte.class); if (value == null) return Optional.empty(); - Byte byteValue = value.byteValue(); - return Optional.ofNullable(byteValue); + return Optional.ofNullable(value); } public Optional hasString(String key) { @@ -78,11 +70,10 @@ public Optional hasString(String key) { } public Optional hasCharacter(String key) { - String value = document.getString(key); + Character value = document.get(key, Character.class); if (value == null) return Optional.empty(); - Character character = value.charAt(0); - return Optional.ofNullable(character); + return Optional.ofNullable(value); } public Optional hasBoolean(String key) { diff --git a/src/main/java/us/mytheria/bloblib/entities/BlobEditor.java b/src/main/java/us/mytheria/bloblib/entities/BlobEditor.java index 57f90823..d04b2c65 100644 --- a/src/main/java/us/mytheria/bloblib/entities/BlobEditor.java +++ b/src/main/java/us/mytheria/bloblib/entities/BlobEditor.java @@ -1,14 +1,17 @@ package us.mytheria.bloblib.entities; +import me.anjoismysign.anjo.entities.Uber; import org.bukkit.ChatColor; import org.bukkit.Material; -import org.bukkit.Sound; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.Nullable; import us.mytheria.bloblib.BlobLib; import us.mytheria.bloblib.entities.inventory.BlobInventory; +import us.mytheria.bloblib.entities.inventory.ObjectBuilder; import us.mytheria.bloblib.entities.inventory.VariableSelector; +import us.mytheria.bloblib.entities.listeners.BlobEditorListener; import us.mytheria.bloblib.entities.listeners.BlobSelectorListener; import us.mytheria.bloblib.managers.SelectorListenerManager; @@ -24,99 +27,153 @@ public class BlobEditor extends VariableSelector implements VariableEditor private final List list; private final Collection collection; private final SelectorListenerManager selectorManager; + private final Consumer addConsumer; + private Consumer removeConsumer; + private Function buildFunction; /** * Creates a new BlobEditor passing a BlobInventory for VariableSelector + * and injects a collection. * * @param the type of the collection * @param blobInventory the BlobInventory * @param builderId the id of the builder * @param dataType the data type of the editor + * @param addConsumer the consumer to add a new element + * @param collection the collection to inject * @return the new BlobEditor */ public static BlobEditor build(BlobInventory blobInventory, UUID builderId, - String dataType) { + String dataType, Consumer addConsumer, + Collection collection) { return new BlobEditor<>(blobInventory, builderId, - dataType); + dataType, collection, addConsumer); + } + + /** + * Creates a new BlobEditor passing a BlobInventory for VariableSelector + * + * @param the type of the collection + * @param blobInventory the BlobInventory + * @param builderId the id of the builder + * @param dataType the data type of the editor + * @param addConsumer the consumer to add a new element + * @return the new BlobEditor + */ + public static BlobEditor build(BlobInventory blobInventory, UUID builderId, + String dataType, Consumer addConsumer) { + return new BlobEditor<>(blobInventory, builderId, + dataType, addConsumer); } /** * Creates a new BlobEditor * - * @param the type of the collection - * @param builderId the id of the builder - * @param dataType the data type of the editor + * @param the type of the collection + * @param builderId the id of the builder + * @param dataType the data type of the editor + * @param addConsumer the consumer to add a new element * @return the new BlobEditor */ - public static BlobEditor DEFAULT(UUID builderId, String dataType) { + public static BlobEditor DEFAULT(UUID builderId, String dataType, + Consumer addConsumer) { return new BlobEditor<>(VariableSelector.DEFAULT(), builderId, - dataType); + dataType, addConsumer); } /** * Creates a new BlobEditor passing specific collection. * - * @param the type of the collection - * @param builderId the id of the builder - * @param dataType the data type of the editor - * @param collection the collection to edit + * @param the type of the collection + * @param builderId the id of the builder + * @param dataType the data type of the editor + * @param collection the collection to edit + * @param addConsumer the consumer to add new elements * @return the new BlobEditor */ public static BlobEditor COLLECTION_INJECTION(UUID builderId, String dataType, - Collection collection) { + Collection collection, + Consumer addConsumer) { return new BlobEditor<>(VariableSelector.DEFAULT(), builderId, - dataType, collection); + dataType, collection, addConsumer); } /** - * Creates a new BlobEditor. + * Creates a new BlobEditor passing an ObjectDirector as a new elements' provider. * - * @param the type of the collection * @param builderId the id of the builder - * @param dataType the data type of the editor + * @param director the ObjectDirector + * @param the type of the collection * @return the new BlobEditor - * @deprecated use {@link #DEFAULT(UUID, String)} instead */ - @Deprecated - public static BlobEditor DEFAULT_ITEMSTACKREADER(UUID builderId, String dataType) { - return new BlobEditor<>(VariableSelector.DEFAULT_ITEMSTACKREADER(), builderId, - dataType); + public static BlobEditor DEFAULT_DIRECTOR(UUID builderId, + ObjectDirector director) { + Uber> uber = Uber.fly(); + uber.talk(BlobEditor.DEFAULT(builderId, director.objectName, player -> { + ObjectManager dropObjectManager = director.getObjectManager(); + BlobEditor editor = dropObjectManager.makeEditor(player); + editor.selectElement(player, element -> uber.thanks().add(element)); + })); + return uber.thanks(); } /** - * Creates a new BlobEditor passing specific collection. + * Creates a new BlobEditor passing an ObjectDirector's ObjectBuilder as a new elements' provider. * - * @param the type of the collection * @param builderId the id of the builder - * @param dataType the data type of the editor * @param collection the collection to edit + * @param director the ObjectDirector + * @param the type of the collection * @return the new BlobEditor - * @deprecated use {@link #COLLECTION_INJECTION(UUID, String, Collection)} instead */ - @Deprecated - public static BlobEditor DEFAULT_ITEMSTACKREADER(UUID builderId, String dataType, - Collection collection) { - return new BlobEditor<>(VariableSelector.DEFAULT_ITEMSTACKREADER(), builderId, - dataType, collection); + public static BlobEditor COLLECTION_INJECTION_BUILDER(UUID builderId, + Collection collection, + ObjectDirector director) { + Uber> uber = Uber.fly(); + if (!director.hasObjectBuilderManager()) + throw new IllegalArgumentException("The director does not have an ObjectBuilderManager. " + + "Implement it in constructor."); + uber.talk(new BlobEditor<>(VariableSelector.DEFAULT(), builderId, + director.objectName, collection, player -> { + ObjectBuilder builder = director.getOrDefaultBuilder(player.getUniqueId()); + builder.open(player); + })); + return uber.thanks(); } protected BlobEditor(BlobInventory blobInventory, UUID builderId, - String dataType) { + String dataType, + Consumer addConsumer) { super(blobInventory, builderId, dataType, null); - list = new ArrayList<>(); - collection = null; - selectorManager = BlobLib.getInstance().getSelectorManager(); + this.selectorManager = BlobLib.getInstance().getSelectorManager(); + this.list = new ArrayList<>(); + this.collection = null; + this.addConsumer = addConsumer; + this.buildFunction = null; } protected BlobEditor(BlobInventory blobInventory, UUID builderId, - String dataType, Collection collection) { + String dataType, Collection collection, + Consumer addConsumer) { super(Objects.requireNonNull(blobInventory, "'blobInventory' cannot be null"), Objects.requireNonNull(builderId, "'builderId' cannot be null"), Objects.requireNonNull(dataType, "'dataType' cannot be null"), null); + this.selectorManager = BlobLib.getInstance().getSelectorManager(); this.collection = collection; - list = null; - selectorManager = BlobLib.getInstance().getSelectorManager(); + this.list = null; + this.addConsumer = addConsumer; + this.buildFunction = null; + } + + /** + * Gets the remove consumer + * + * @return the remove consumer + */ + @Nullable + public Consumer getRemoveConsumer() { + return removeConsumer; } /** @@ -215,7 +272,10 @@ public List> customPage(int page, int itemsPerPage, Function(function.apply(get), get)); + ItemStack itemStack = function.apply(get); + if (itemStack == null) + continue; + values.add(new VariableValue<>(itemStack, get)); } catch (IndexOutOfBoundsException e) { break; } @@ -224,65 +284,63 @@ public List> customPage(int page, int itemsPerPage, Function consumer, String timerMessageKey) { + if (buildFunction != null) { + selectElement(player, consumer, timerMessageKey, buildFunction); return; - list.remove(t); + } + loadPage(getPage(), true); + selectorManager.addEditorListener(player, BlobEditorListener.wise(player, + consumer, timerMessageKey, + this)); } /** - * Removes an element from the list. Will not close the - * inventory unless input is null.You have to manually - * call player.closeInventory() inside the consumer. + * Selects an object from the editor. * - * @param player the player - * @param consumer the consumer to accept the element + * @param player The player that selected the object. + * @param consumer The consumer that will be called when the object is selected. + * @param timerMessageKey The key of the message that will be sent to the player when the timer starts. + * @param function The function that will be called to customize the ItemStack. */ - public void removeElement(Player player, Consumer consumer) { - loadPage(getPage(), true); + @Override + public void selectElement(Player player, Consumer consumer, String timerMessageKey, Function function) { + loadCustomPage(getPage(), true, function); selectorManager.addSelectorListener(player, BlobSelectorListener.wise(player, - input -> { - player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 1, 1); - remove(input); - consumer.accept(input); - }, "Editor.Remove", + consumer, timerMessageKey, this)); } /** - * Removes an element from the list. Will not close the - * inventory unless input is null.You have to manually - * call player.closeInventory() inside the consumer. + * Will attempt to remove the element from the collection + * based on the loaded remove consumer. * - * @param player the player - * @param consumer the consumer to accept the element - * @param function the function to get the ItemStack to display - * the elements. + * @param player The player to remove the element from. */ - public void removeElement(Player player, Consumer consumer, Function function) { - loadCustomPage(getPage(), true, function); - selectorManager.addSelectorListener(player, BlobSelectorListener.wise(player, - input -> { - player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 1, 1); - remove(input); - consumer.accept(input); - }, "Editor.Remove", - this)); + public void removeElement(Player player) { + if (removeConsumer == null) + return; + removeElement(player, removeConsumer); } /** - * add an element to the list + * Removes an object from the editor. + * The change will be reflected in the collection immediately. * - * @param element the element to add + * @param t The object to remove. */ - public void addElement(T element) { + @Override + public void remove(T t) { if (list == null) return; - list.add(element); + list.remove(t); } /** @@ -305,9 +363,10 @@ public int getTotalPages() { } /** - * add an element to the list + * Adds a new object to the editor. + * The change will be reflected in the collection immediately. * - * @param t the element to add + * @param t The object to add. */ @Override public void add(T t) { @@ -325,4 +384,115 @@ public List getList() { } return list; } + + /** + * @param slot the slot to check + * @return true if the slot is an add element button, false otherwise + */ + public boolean isAddElementButton(int slot) { + return Objects.requireNonNull(getSlots("Add-Element"), + "Add-Element button not found").contains(slot); + } + + /** + * @param slot the slot to check + * @return true if the slot is a remove element button, false otherwise + */ + public boolean isRemoveElementButton(int slot) { + return Objects.requireNonNull(getSlots("Remove-Element"), + "Remove-Element button not found").contains(slot); + } + + /** + * Will attempt to add an element to the collection + * through the following player. + * + * @param player The player to manage the action. + */ + public void addElement(Player player) { + if (addConsumer == null) + return; + addConsumer.accept(player); + } + + /** + * Will make the editor listen to the player + * for actions such as navigating through pages, + * adding and removing elements. + * If anything of the previous is done, + * the editor will adapt to the player for that + * specific action. + * If player selects an element, nothing will happen. + * + * @param player The player to manage the editor. + */ + @Override + public void manage(Player player, Consumer removeConsumer) { + if (buildFunction != null) { + manage(player, buildFunction, removeConsumer); + return; + } + loadPage(1, true); + if (!selectorManager.addEditorListener(player, + BlobEditorListener.wise(player, + input -> { + manageWithCache(player); + }, null, + this))) + return; + this.removeConsumer = removeConsumer; + } + + /** + * Will make the editor listen to the player + * for actions such as navigating through pages, + * adding and removing elements. + * If anything of the previous is done, + * the editor will adapt to the player for that + * specific action. + * If player selects an element, nothing will happen. + * + * @param player The player to manage the editor. + * @param function the function that loads ItemStacks in inventory + */ + @Override + public void manage(Player player, Function function, + Consumer removeConsumer) { + loadCustomPage(1, true, function); + if (!selectorManager.addEditorListener(player, + BlobEditorListener.wise(player, + input -> { + manageWithCache(player); + }, null, + this))) + return; + setBuildFunction(function); + this.removeConsumer = removeConsumer; + } + + /** + * Will make the editor listen to the player + * using the cache for actions such as navigating through pages, + * adding and removing elements. + * + * @param player The player to manage the editor. + */ + public void manageWithCache(Player player) { + if (removeConsumer == null) + throw new IllegalStateException("removeConsumer is null"); + manage(player, removeConsumer); + } + + /** + * Will update the build function. + * If null, selectElement will use the default build function. + * If not null, selectElement will use the new build function. + * It is automaitcally called when {@link #manage(Player, Function, Consumer)} + * is called. + * + * @param buildFunction the new build function + */ + public void setBuildFunction(Function buildFunction) { + this.buildFunction = buildFunction; + } } \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/entities/BlobExecutor.java b/src/main/java/us/mytheria/bloblib/entities/BlobExecutor.java index f7d09ac4..c1f1de4f 100644 --- a/src/main/java/us/mytheria/bloblib/entities/BlobExecutor.java +++ b/src/main/java/us/mytheria/bloblib/entities/BlobExecutor.java @@ -4,9 +4,9 @@ import org.bukkit.Bukkit; import org.bukkit.command.*; import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Nullable; -import us.mytheria.bloblib.BlobLibAssetAPI; -import us.mytheria.bloblib.managers.BlobPlugin; +import us.mytheria.bloblib.api.BlobLibMessageAPI; import java.util.ArrayList; import java.util.List; @@ -24,7 +24,7 @@ public class BlobExecutor implements CommandExecutor, TabCompleter { private Consumer debug; private final List callers; - public BlobExecutor(BlobPlugin plugin, String commandName) { + public BlobExecutor(JavaPlugin plugin, String commandName) { commandName = commandName.toLowerCase(); PluginCommand command = plugin.getCommand(commandName); this.pluginCommand = command; @@ -50,7 +50,7 @@ public BlobExecutor(BlobPlugin plugin, String commandName) { }; this.callers = new ArrayList<>(); if (command == null) { - plugin.getAnjoLogger().debug("Command " + commandName + " not found inside plugin.yml"); + plugin.getLogger().log(Level.WARNING, "Command " + commandName + " not found inside plugin.yml"); return; } command.getAliases().forEach(alias -> callers.add(alias.toLowerCase())); @@ -166,6 +166,7 @@ public boolean hasDebugPermission(CommandSender sender) { /** * Will check if CommandSender has the provided permission. * If not, will automatically send a ReferenceBlobMessage with the key of blobMessageKey. + * If blobMessageKey is null, will not send any BlobMessage. * * @param sender The CommandSender to check. * @param permission The permission to check. @@ -177,7 +178,8 @@ public boolean hasPermission(CommandSender sender, String blobMessageKey) { boolean has = sender.hasPermission(permission); if (!has) - BlobLibAssetAPI.getMessage(blobMessageKey).toCommandSender(sender); + if (blobMessageKey != null) + BlobLibMessageAPI.getInstance().getMessage(blobMessageKey).toCommandSender(sender); return has; } @@ -204,7 +206,7 @@ public boolean hasPermission(CommandSender sender, */ public boolean isInstanceOfPlayer(CommandSender sender, String blobMessageKey) { if (!(sender instanceof Player player)) { - BlobLibAssetAPI.getMessage(blobMessageKey).toCommandSender(sender); + BlobLibMessageAPI.getInstance().getMessage(blobMessageKey).toCommandSender(sender); return false; } return true; diff --git a/src/main/java/us/mytheria/bloblib/entities/BlobFileManager.java b/src/main/java/us/mytheria/bloblib/entities/BlobFileManager.java index ca77df53..5a0419a8 100644 --- a/src/main/java/us/mytheria/bloblib/entities/BlobFileManager.java +++ b/src/main/java/us/mytheria/bloblib/entities/BlobFileManager.java @@ -5,6 +5,7 @@ import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import us.mytheria.bloblib.managers.BlobPlugin; import us.mytheria.bloblib.managers.Manager; import us.mytheria.bloblib.managers.ManagerDirector; import us.mytheria.bloblib.utilities.ResourceUtil; @@ -20,10 +21,25 @@ * A BlobFileManager will manage all files related to inventories, * messages, sounds and future objects that BlobLib will support. */ -public class BlobFileManager extends Manager { +public class BlobFileManager extends Manager implements IFileManager { private final File pluginDirectory; private final HashMap files; - private final String lowercased = getPlugin().getName().toLowerCase(); + private final String lowercased; + + /** + * Will make a BlobFileManager from a ManagerDirector. + * Doesn't work with GenericManagerDirector + * since their #getPlugin() only works after + * ManagerDirector constructor. + * + * @param managerDirector the manager director + * @return the BlobFileManager + */ + public static BlobFileManager of(ManagerDirector managerDirector) { + BlobPlugin plugin = managerDirector.getPlugin(); + return new BlobFileManager(managerDirector, "plugins/" + + plugin.getDataFolder().getPath(), plugin); + } /** * creates a new BlobFileManager @@ -32,17 +48,23 @@ public class BlobFileManager extends Manager { * @param pluginDirectoryPathname the path to the plugin directory */ public BlobFileManager(ManagerDirector managerDirector, - String pluginDirectoryPathname) { + String pluginDirectoryPathname, + JavaPlugin plugin) { super(managerDirector); + this.lowercased = plugin.getName().toLowerCase(); this.files = new HashMap<>(); this.pluginDirectory = new File(pluginDirectoryPathname); addFile("messages", new File(pluginDirectory.getPath() + "/BlobMessage")); addFile("sounds", new File(pluginDirectory.getPath() + "/BlobSound")); - addFile("inventories", new File(pluginDirectory.getPath() + "/BlobInventory")); + addFile("blobInventories", new File(pluginDirectory.getPath() + "/BlobInventory")); + addFile("metaBlobInventories", new File(pluginDirectory.getPath() + "/MetaBlobInventory")); + addFile("actions", new File(pluginDirectory.getPath() + "/Action")); addFile("defaultSounds", new File(soundsDirectory().getPath() + "/" + lowercased + "_sounds.yml")); addFile("defaultMessages", new File(messagesDirectory().getPath() + "/" + lowercased + "_lang.yml")); - addFile("defaultInventories", new File(inventoriesDirectory().getPath() + "/" + lowercased + "_inventories.yml")); - loadFiles(); + addFile("defaultBlobInventories", new File(inventoriesDirectory().getPath() + "/" + lowercased + "_inventories.yml")); + addFile("defaultMetaBlobInventories", new File(metaInventoriesDirectory().getPath() + "/" + lowercased + "_meta_inventories.yml")); + addFile("defaultActions", new File(actionsDirectory().getPath() + "/" + lowercased + "_actions.yml")); + loadFiles(plugin); } /** @@ -96,10 +118,9 @@ public boolean updateYAML(File file) { String fileName = FilenameUtils.removeExtension(file.getName()); try { boolean isFresh = file.createNewFile(); - if (isFresh) - ResourceUtil.updateYml(file.getParentFile(), - "/temp" + fileName + ".yml", - fileName + ".yml", file, getPlugin()); + ResourceUtil.updateYml(file.getParentFile(), + "/temp" + fileName + ".yml", + fileName + ".yml", file, getPlugin()); return isFresh; } catch (IOException e) { e.printStackTrace(); @@ -156,29 +177,39 @@ public Optional searchFile(String key) { /** * Loads all files */ - public void loadFiles() { + public void loadFiles(JavaPlugin plugin) { try { if (!pluginDirectory.exists()) pluginDirectory.mkdir(); if (!messagesDirectory().exists()) messagesDirectory().mkdir(); if (!soundsDirectory().exists()) soundsDirectory().mkdir(); if (!inventoriesDirectory().exists()) inventoriesDirectory().mkdir(); + if (!metaInventoriesDirectory().exists()) metaInventoriesDirectory().mkdir(); + if (!actionsDirectory().exists()) actionsDirectory().mkdir(); /////////////////////////////////////////// - - JavaPlugin main = getPlugin(); - Optional soundsOptional = Optional.ofNullable(main.getResource(lowercased + "_sounds.yml")); + Optional soundsOptional = Optional.ofNullable(plugin.getResource(lowercased + "_sounds.yml")); if (soundsOptional.isPresent()) { getDefaultSounds().createNewFile(); - ResourceUtil.updateYml(soundsDirectory(), "/temp" + lowercased + "_sounds.yml", lowercased + "_sounds.yml", getDefaultSounds(), getPlugin()); + ResourceUtil.updateYml(soundsDirectory(), "/temp" + lowercased + "_sounds.yml", lowercased + "_sounds.yml", getDefaultSounds(), plugin); } - Optional langOptional = Optional.ofNullable(main.getResource(lowercased + "_lang.yml")); + Optional langOptional = Optional.ofNullable(plugin.getResource(lowercased + "_lang.yml")); if (langOptional.isPresent()) { getDefaultMessages().createNewFile(); - ResourceUtil.updateYml(messagesDirectory(), "/temp" + lowercased + "_lang.yml", lowercased + "_lang.yml", getDefaultMessages(), getPlugin()); + ResourceUtil.updateYml(messagesDirectory(), "/temp" + lowercased + "_lang.yml", lowercased + "_lang.yml", getDefaultMessages(), plugin); } - Optional inventoriesOptional = Optional.ofNullable(main.getResource(lowercased + "_inventories.yml")); + Optional inventoriesOptional = Optional.ofNullable(plugin.getResource(lowercased + "_inventories.yml")); if (inventoriesOptional.isPresent()) { getDefaultInventories().createNewFile(); - ResourceUtil.updateYml(inventoriesDirectory(), "/temp" + lowercased + "_inventories.yml", lowercased + "_inventories.yml", getDefaultInventories(), getPlugin()); + ResourceUtil.updateYml(inventoriesDirectory(), "/temp" + lowercased + "_inventories.yml", lowercased + "_inventories.yml", getDefaultInventories(), plugin); + } + Optional metaInventoriesOptional = Optional.ofNullable(plugin.getResource(lowercased + "_meta_inventories.yml")); + if (metaInventoriesOptional.isPresent()) { + getDefaultMetaInventories().createNewFile(); + ResourceUtil.updateYml(metaInventoriesDirectory(), "/temp" + lowercased + "_meta_inventories.yml", lowercased + "_meta_inventories.yml", getDefaultMetaInventories(), plugin); + } + Optional actionsOptional = Optional.ofNullable(plugin.getResource(lowercased + "_actions.yml")); + if (actionsOptional.isPresent()) { + getDefaultActions().createNewFile(); + ResourceUtil.updateYml(actionsDirectory(), "/temp" + lowercased + "_actions.yml", lowercased + "_actions.yml", getDefaultActions(), plugin); } } catch (Exception e) { e.printStackTrace(); @@ -280,7 +311,17 @@ public File soundsDirectory() { */ @NotNull public File inventoriesDirectory() { - return getFile("inventories"); + return getFile("blobInventories"); + } + + @NotNull + public File metaInventoriesDirectory() { + return getFile("metaBlobInventories"); + } + + @NotNull + public File actionsDirectory() { + return getFile("actions"); } /** @@ -310,6 +351,16 @@ public File getDefaultSounds() { */ @NotNull public File getDefaultInventories() { - return getFile("defaultInventories"); + return getFile("defaultBlobInventories"); + } + + @NotNull + public File getDefaultMetaInventories() { + return getFile("defaultMetaInventories"); + } + + @NotNull + public File getDefaultActions() { + return getFile("defaultActions"); } } diff --git a/src/main/java/us/mytheria/bloblib/entities/BlobListener.java b/src/main/java/us/mytheria/bloblib/entities/BlobListener.java new file mode 100644 index 00000000..16d408c9 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/BlobListener.java @@ -0,0 +1,46 @@ +package us.mytheria.bloblib.entities; + +import org.bukkit.Bukkit; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; + +public interface BlobListener extends Listener { + /** + * Will get the listener manager that this listener is registered to. + * + * @return the listener manager that this listener is registered to. + */ + ListenerManager getListenerManager(); + + /** + * Will reload the listener. + * It will register if needed or unregister otherwise. + */ + default void reload() { + unregister(); + if (checkIfShouldRegister()) + register(); + } + + /** + * Will unregister the listener. + */ + default void unregister() { + HandlerList.unregisterAll(this); + } + + /** + * Will register the listener. + * Note: This will not check if the listener should be registered. + */ + default void register() { + Bukkit.getPluginManager().registerEvents(this, getListenerManager().getPlugin()); + } + + /** + * Will check if the listener should be registered. + * + * @return true if the listener should be registered, false otherwise. + */ + boolean checkIfShouldRegister(); +} diff --git a/src/main/java/us/mytheria/bloblib/entities/BlobMessageReader.java b/src/main/java/us/mytheria/bloblib/entities/BlobMessageReader.java index 3eae4ded..720a50d8 100644 --- a/src/main/java/us/mytheria/bloblib/entities/BlobMessageReader.java +++ b/src/main/java/us/mytheria/bloblib/entities/BlobMessageReader.java @@ -1,7 +1,8 @@ package us.mytheria.bloblib.entities; import org.bukkit.configuration.ConfigurationSection; -import us.mytheria.bloblib.BlobLibAssetAPI; +import org.jetbrains.annotations.Nullable; +import us.mytheria.bloblib.api.BlobLibMessageAPI; import us.mytheria.bloblib.entities.message.*; import us.mytheria.bloblib.utilities.TextColor; @@ -22,79 +23,88 @@ public class BlobMessageReader { */ public static SerialBlobMessage read(ConfigurationSection section) { String type = section.getString("Type"); + @Nullable String locale = section.getString("Locale", null); Optional sound = section.contains("BlobSound") ? BlobSoundReader.parse(section) : Optional.empty(); switch (type) { case "ACTIONBAR" -> { if (!section.contains("Message")) - throw new IllegalArgumentException("'Message' is required for ACTIONBAR messages."); - return new BlobActionbarMessage(TextColor.PARSE(section.getString("Message")), sound.orElse(null)); + throw new IllegalArgumentException("'Message' is required for ACTIONBAR messages at " + section.getCurrentPath()); + return new BlobActionbarMessage(TextColor.PARSE(section.getString("Message")), + sound.orElse(null), + locale); } case "TITLE" -> { if (!section.contains("Title")) - throw new IllegalArgumentException("'Title' is required for TITLE messages."); + throw new IllegalArgumentException("'Title' is required for TITLE messages at " + section.getCurrentPath()); if (!section.contains("Subtitle")) - throw new IllegalArgumentException("'Subtitle' is required for TITLE messages."); + throw new IllegalArgumentException("'Subtitle' is required for TITLE messages at " + section.getCurrentPath()); int fadeIn = section.getInt("FadeIn", 10); int stay = section.getInt("Stay", 40); int fadeOut = section.getInt("FadeOut", 10); return new BlobTitleMessage(TextColor.PARSE(section.getString("Title")), TextColor.PARSE(section.getString("Subtitle")), - fadeIn, stay, fadeOut, sound.orElse(null)); + fadeIn, stay, fadeOut, sound.orElse(null), + locale); } case "CHAT" -> { if (!section.contains("Message")) - throw new IllegalArgumentException("'Message' is required for CHAT messages."); - return new BlobChatMessage(TextColor.PARSE(section.getString("Message")), sound.orElse(null)); + throw new IllegalArgumentException("'Message' is required for CHAT messages at " + section.getCurrentPath()); + return new BlobChatMessage(TextColor.PARSE(section.getString("Message")), + sound.orElse(null), + locale); } case "ACTIONBAR_TITLE" -> { if (!section.contains("Title")) - throw new IllegalArgumentException("'Title' is required for ACTIONBAR_TITLE messages."); + throw new IllegalArgumentException("'Title' is required for ACTIONBAR_TITLE messages at " + section.getCurrentPath()); if (!section.contains("Subtitle")) - throw new IllegalArgumentException("'Subtitle' is required for ACTIONBAR_TITLE messages."); + throw new IllegalArgumentException("'Subtitle' is required for ACTIONBAR_TITLE messages at " + section.getCurrentPath()); if (!section.contains("Actionbar")) - throw new IllegalArgumentException("'Actionbar' is required for ACTIONBAR_TITLE messages."); + throw new IllegalArgumentException("'Actionbar' is required for ACTIONBAR_TITLE messages at " + section.getCurrentPath()); int fadeIn = section.getInt("FadeIn", 10); int stay = section.getInt("Stay", 40); int fadeOut = section.getInt("FadeOut", 10); return new BlobActionbarTitleMessage(TextColor.PARSE(section.getString("Actionbar")), TextColor.PARSE(section.getString("Title")), TextColor.PARSE(section.getString("Subtitle")), - fadeIn, stay, fadeOut, sound.orElse(null)); + fadeIn, stay, fadeOut, sound.orElse(null), + locale); } case "CHAT_ACTIONBAR" -> { if (!section.contains("Chat")) - throw new IllegalArgumentException("'Chat' is required for CHAT_ACTIONBAR messages."); + throw new IllegalArgumentException("'Chat' is required for CHAT_ACTIONBAR messages at " + section.getCurrentPath()); if (!section.contains("Actionbar")) - throw new IllegalArgumentException("'Actionbar' is required for CHAT_ACTIONBAR messages."); + throw new IllegalArgumentException("'Actionbar' is required for CHAT_ACTIONBAR messages at " + section.getCurrentPath()); return new BlobChatActionbarMessage(TextColor.PARSE(section.getString("Chat")), TextColor.PARSE(section.getString("Actionbar")), - sound.orElse(null)); + sound.orElse(null), + locale); } case "CHAT_TITLE" -> { if (!section.contains("Chat")) - throw new IllegalArgumentException("'Chat' is required for CHAT_TITLE messages."); + throw new IllegalArgumentException("'Chat' is required for CHAT_TITLE messages at " + section.getCurrentPath()); if (!section.contains("Title")) - throw new IllegalArgumentException("'Title' is required for CHAT_TITLE messages."); + throw new IllegalArgumentException("'Title' is required for CHAT_TITLE messages at " + section.getCurrentPath()); if (!section.contains("Subtitle")) - throw new IllegalArgumentException("'Subtitle' is required for CHAT_TITLE messages."); + throw new IllegalArgumentException("'Subtitle' is required for CHAT_TITLE messages at " + section.getCurrentPath()); int fadeIn = section.getInt("FadeIn", 10); int stay = section.getInt("Stay", 40); int fadeOut = section.getInt("FadeOut", 10); return new BlobChatTitleMessage(TextColor.PARSE(section.getString("Chat")), TextColor.PARSE(section.getString("Title")), TextColor.PARSE(section.getString("Subtitle")), - fadeIn, stay, fadeOut, sound.orElse(null)); + fadeIn, stay, fadeOut, sound.orElse(null), + locale); } case "CHAT_ACTIONBAR_TITLE" -> { if (!section.contains("Chat")) - throw new IllegalArgumentException("'Chat' is required for CHAT_ACTIONBAR_TITLE messages."); + throw new IllegalArgumentException("'Chat' is required for CHAT_ACTIONBAR_TITLE messages at " + section.getCurrentPath()); if (!section.contains("Actionbar")) - throw new IllegalArgumentException("'Actionbar' is required for CHAT_ACTIONBAR_TITLE messages."); + throw new IllegalArgumentException("'Actionbar' is required for CHAT_ACTIONBAR_TITLE messages at " + section.getCurrentPath()); if (!section.contains("Title")) - throw new IllegalArgumentException("'Title' is required for CHAT_ACTIONBAR_TITLE messages."); + throw new IllegalArgumentException("'Title' is required for CHAT_ACTIONBAR_TITLE messages at " + section.getCurrentPath()); if (!section.contains("Subtitle")) - throw new IllegalArgumentException("'Subtitle' is required for CHAT_ACTIONBAR_TITLE messages."); + throw new IllegalArgumentException("'Subtitle' is required for CHAT_ACTIONBAR_TITLE messages at " + section.getCurrentPath()); int fadeIn = section.getInt("FadeIn", 10); int stay = section.getInt("Stay", 40); int fadeOut = section.getInt("FadeOut", 10); @@ -102,9 +112,11 @@ public static SerialBlobMessage read(ConfigurationSection section) { TextColor.PARSE(section.getString("Actionbar")), TextColor.PARSE(section.getString("Title")), TextColor.PARSE(section.getString("Subtitle")), - fadeIn, stay, fadeOut, sound.orElse(null)); + fadeIn, stay, fadeOut, sound.orElse(null), + locale); } - default -> throw new IllegalArgumentException("Invalid message type: " + type); + default -> + throw new IllegalArgumentException("Invalid message type: '" + type + "' at " + section.getCurrentPath()); } } @@ -120,7 +132,7 @@ public static Optional parse(ConfigurationSection parentConfigurati if (!parentConfigurationSection.contains("BlobMessage")) return Optional.empty(); if (parentConfigurationSection.isString("BlobMessage")) - return Optional.ofNullable(BlobLibAssetAPI.getMessage(parentConfigurationSection.getString("BlobMessage"))); + return Optional.ofNullable(BlobLibMessageAPI.getInstance().getMessage(parentConfigurationSection.getString("BlobMessage"))); return Optional.of(read(parentConfigurationSection.getConfigurationSection("BlobMessage"))); } @@ -135,6 +147,6 @@ public static Optional readReference(ConfigurationSection return Optional.empty(); if (!section.isString("BlobMessage")) throw new IllegalArgumentException("'BlobMessage' must be a String"); - return Optional.ofNullable(BlobLibAssetAPI.getMessage(section.getString("BlobMessage"))); + return Optional.ofNullable(BlobLibMessageAPI.getInstance().getMessage(section.getString("BlobMessage"))); } } diff --git a/src/main/java/us/mytheria/bloblib/entities/BlobObject.java b/src/main/java/us/mytheria/bloblib/entities/BlobObject.java index a5f1ef2d..19693340 100644 --- a/src/main/java/us/mytheria/bloblib/entities/BlobObject.java +++ b/src/main/java/us/mytheria/bloblib/entities/BlobObject.java @@ -12,7 +12,7 @@ * And example of this is link {@link us.mytheria.bloblib.entities.currency.Currency}. * and link {@link us.mytheria.bloblib.entities.currency.CurrencyBuilder}. */ -public interface BlobObject { +public interface BlobObject extends Comparable { /** * The key to identify the object by, inside the ObjectManager. * @@ -32,6 +32,17 @@ public interface BlobObject { */ File saveToFile(File directory); + /** + * By providing a File directory, will automatically create an instance + * of the file that can be used as a YAML file. + * + * @param directory The directory to create the file in + * @return The file + */ + default File instanceFile(File directory) { + return new File(directory + "/" + getKey() + ".yml"); + } + /** * Should return a new ObjectBuilder that will automatically * apply the attributes saved inside this BlobObject. @@ -41,4 +52,8 @@ public interface BlobObject { default ObjectBuilder edit() { return null; } + + default int compareTo(BlobObject o) { + return getKey().compareTo(o.getKey()); + } } diff --git a/src/main/java/us/mytheria/bloblib/entities/BlobSelector.java b/src/main/java/us/mytheria/bloblib/entities/BlobSelector.java new file mode 100644 index 00000000..9d867016 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/BlobSelector.java @@ -0,0 +1,279 @@ +package us.mytheria.bloblib.entities; + +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import us.mytheria.bloblib.BlobLib; +import us.mytheria.bloblib.entities.inventory.BlobInventory; +import us.mytheria.bloblib.entities.inventory.VariableSelector; +import us.mytheria.bloblib.entities.listeners.BlobSelectorListener; +import us.mytheria.bloblib.entities.listeners.EditorActionType; +import us.mytheria.bloblib.managers.SelectorListenerManager; + +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * @author anjoismysign + * A BlobEditor is a VariableEditor that can be used to edit a collection. + */ +public class BlobSelector extends VariableSelector implements VariableFiller { + private final List list; + private final Collection collection; + private final SelectorListenerManager selectorManager; + + /** + * Creates a new BlobEditor passing a BlobInventory for VariableSelector + * and injects a collection. + * + * @param the type of the collection + * @param blobInventory the BlobInventory + * @param builderId the id of the builder + * @param dataType the data type of the editor + * @param collection the collection to inject + * @return the new BlobEditor + */ + public static BlobSelector build(BlobInventory blobInventory, UUID builderId, + String dataType, Collection collection) { + return new BlobSelector<>(blobInventory, builderId, + dataType, collection); + } + + + /** + * Creates a new BlobEditor passing a BlobInventory for VariableSelector + * + * @param the type of the collection + * @param blobInventory the BlobInventory + * @param builderId the id of the builder + * @param dataType the data type of the editor + * @return the new BlobEditor + */ + public static BlobSelector build(BlobInventory blobInventory, UUID builderId, + String dataType) { + return new BlobSelector<>(blobInventory, builderId, + dataType); + } + + /** + * Creates a new BlobEditor + * + * @param the type of the collection + * @param builderId the id of the builder + * @param dataType the data type of the editor + * @return the new BlobEditor + */ + public static BlobSelector DEFAULT(UUID builderId, String dataType) { + return new BlobSelector<>(VariableSelector.DEFAULT(), builderId, + dataType); + } + + /** + * Creates a new BlobEditor passing specific collection. + * + * @param the type of the collection + * @param builderId the id of the builder + * @param dataType the data type of the editor + * @param collection the collection to edit + * @return the new BlobEditor + */ + public static BlobSelector COLLECTION_INJECTION(UUID builderId, String dataType, + Collection collection) { + return new BlobSelector<>(VariableSelector.DEFAULT(), builderId, + dataType, collection); + } + + protected BlobSelector(BlobInventory blobInventory, UUID builderId, + String dataType) { + super(blobInventory, builderId, dataType, null); + this.selectorManager = BlobLib.getInstance().getSelectorManager(); + this.list = new ArrayList<>(); + this.collection = null; + } + + protected BlobSelector(BlobInventory blobInventory, UUID builderId, + String dataType, Collection collection) { + super(Objects.requireNonNull(blobInventory, "'blobInventory' cannot be null"), + Objects.requireNonNull(builderId, "'builderId' cannot be null"), + Objects.requireNonNull(dataType, "'dataType' cannot be null"), + null); + this.selectorManager = BlobLib.getInstance().getSelectorManager(); + this.collection = collection; + this.list = null; + } + + /** + * any actions that should be executed inside the constructor on super call + */ + @Override + public void loadInConstructor() { + } + + /** + * loads the page with the given page number + * + * @param page the page number + * @param whiteBackgroundRefill if the background should be refilled + */ + @Override + public void loadPage(int page, boolean whiteBackgroundRefill) { + if (page < 1) { + return; + } + if (getTotalPages() < page) { + return; + } + if (whiteBackgroundRefill) + refillButton("White-Background"); + clearValues(); + List> values = this.page(page, getItemsPerPage()); + for (int i = 0; i < values.size(); i++) { + setValue(i, values.get(i)); + } + } + + /** + * loads the page with the given page number + * + * @param page the page number + * @param refill if the background should be refilled + * @param function the function to apply + */ + public void loadCustomPage(int page, boolean refill, Function function) { + if (page < 1) { + return; + } + if (getTotalPages() < page) { + return; + } + if (refill) + refillButton("White-Background"); + clearValues(); + List> values = this.customPage(page, getItemsPerPage(), function); + for (int i = 0; i < values.size(); i++) { + setValue(i, values.get(i)); + } + } + + /** + * returns the page with the given page number without loading + * + * @param page the page number + * @param itemsPerPage the items per page + */ + @Override + public List> page(int page, int itemsPerPage) { + int start = (page - 1) * itemsPerPage; + int end = start + (itemsPerPage); + ArrayList> values = new ArrayList<>(); + for (int i = start; i < end; i++) { + T get; + try { + get = getList().get(i); + ItemStack itemStack = new ItemStack(Material.LEATHER_CHESTPLATE); + ItemMeta itemMeta = itemStack.getItemMeta(); + itemMeta.setDisplayName(ChatColor.GOLD + get.toString()); + itemStack.setItemMeta(itemMeta); + values.add(new VariableValue<>(itemStack, get)); + } catch (IndexOutOfBoundsException e) { + break; + } + } + return values; + } + + /** + * returns specific page with provided function without loading + * + * @param page the page + * @param itemsPerPage the items per page + * @param function the function to apply + * @return the list of values + */ + public List> customPage(int page, int itemsPerPage, Function function) { + int start = (page - 1) * itemsPerPage; + int end = start + (itemsPerPage); + ArrayList> values = new ArrayList<>(); + for (int i = start; i < end; i++) { + T get; + try { + get = getList().get(i); + ItemStack itemStack = function.apply(get); + if (itemStack == null) + continue; + values.add(new VariableValue<>(itemStack, get)); + } catch (IndexOutOfBoundsException e) { + break; + } + } + return values; + } + + public void selectElement(Player player, Consumer consumer, String timerMessageKey) { + loadPage(getPage(), true); + selectorManager.addSelectorListener(player, BlobSelectorListener.wise(player, + consumer, timerMessageKey, + this)); + } + + public void selectElement(Player player, Consumer consumer, String timerMessageKey, Function function) { + loadCustomPage(getPage(), true, function); + selectorManager.addSelectorListener(player, BlobSelectorListener.wise(player, + consumer, timerMessageKey, + this)); + } + + /** + * @return the total pages + */ + @Override + public int totalPages(int itemsPerPage) { + return (int) Math.ceil((double) getList().size() / (double) itemsPerPage); + } + + /** + * @return the total pages using getItemsPerPage() method + */ + @Override + public int getTotalPages() { + int totalPages = totalPages(getItemsPerPage()); + if (totalPages == 0) + totalPages = 1; + return totalPages; + } + + /** + * @return the list + */ + public List getList() { + if (collection != null) { + return new ArrayList<>(collection); + } + return list; + } + + public void process(EditorActionType input, Player player) { + switch (input) { + case NEXT_PAGE -> { + nextPage(); + } + case PREVIOUS_PAGE -> { + previousPage(); + } + default -> { + throw new IllegalStateException("Unexpected value: " + input + "\n" + + "Contact BlobLib developer."); + } + } + } + + public void process(EditorActionType input) { + Player player = getPlayer(); + if (player == null) + return; + process(input, player); + } +} \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/entities/BlobSerializable.java b/src/main/java/us/mytheria/bloblib/entities/BlobSerializable.java new file mode 100644 index 00000000..66b170e7 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/BlobSerializable.java @@ -0,0 +1,29 @@ +package us.mytheria.bloblib.entities; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +/** + * Represents a BinarySerializable that is associated with a player + */ +public interface BlobSerializable extends BinarySerializable { + /** + * Will return the player that is associated + * Might be null if called after the player has left + * the session or if identification is not player name or uuid. + * + * @return the player + */ + @Nullable + default Player getPlayer() { + int length = blobCrudable().getIdentification().length(); + if (length == 36) { + return Bukkit.getPlayer(UUID.fromString(blobCrudable().getIdentification())); + } else { + return Bukkit.getPlayer(blobCrudable().getIdentification()); + } + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/BlobSerializableManager.java b/src/main/java/us/mytheria/bloblib/entities/BlobSerializableManager.java new file mode 100644 index 00000000..0edd9474 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/BlobSerializableManager.java @@ -0,0 +1,197 @@ +package us.mytheria.bloblib.entities; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.AsyncPlayerPreLoginEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.jetbrains.annotations.Nullable; +import us.mytheria.bloblib.managers.BlobPlugin; +import us.mytheria.bloblib.managers.Manager; +import us.mytheria.bloblib.managers.ManagerDirector; +import us.mytheria.bloblib.storage.BlobCrudManager; +import us.mytheria.bloblib.storage.IdentifierType; +import us.mytheria.bloblib.storage.StorageType; +import us.mytheria.bloblib.utilities.BlobCrudManagerFactory; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import java.util.function.Function; + +public class BlobSerializableManager extends Manager implements Listener { + protected final HashMap serializables; + private final HashSet saving; + protected BlobCrudManager crudManager; + private final BlobPlugin plugin; + private final Function generator; + private final @Nullable Function joinEvent; + private final @Nullable Function quitEvent; + + protected BlobSerializableManager(ManagerDirector managerDirector, Function newBorn, + Function generator, + String crudableName, boolean logActivity, + @Nullable Function joinEvent, + @Nullable Function quitEvent) { + super(managerDirector); + plugin = managerDirector.getPlugin(); + Bukkit.getPluginManager().registerEvents(this, plugin); + serializables = new HashMap<>(); + this.generator = generator; + this.joinEvent = joinEvent; + this.quitEvent = quitEvent; + saving = new HashSet<>(); + crudManager = BlobCrudManagerFactory.PLAYER(plugin, crudableName, newBorn, logActivity); + reload(); + } + + @Override + public void unload() { + saveAll(); + } + + @Override + public void reload() { + unload(); + } + + @EventHandler + public void onPrejoin(AsyncPlayerPreLoginEvent e) { + UUID uuid = e.getUniqueId(); + if (saving.contains(uuid)) + e.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, "You are already saving your data, please try again in a few seconds."); + } + + @EventHandler + public void onJoin(PlayerJoinEvent e) { + Player player = e.getPlayer(); + UUID uuid = player.getUniqueId(); + CompletableFuture future = new CompletableFuture<>(); + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + if (player == null || !player.isOnline()) { + future.completeExceptionally(new NullPointerException("Player is null")); + return; + } + BlobCrudable crudable = crudManager.read(uuid.toString()); + future.complete(crudable); + }); + future.thenAccept(crudable -> { + if (player == null || !player.isOnline()) + return; + T applied = generator.apply(crudable); + serializables.put(uuid, applied); + if (joinEvent == null) + return; + Bukkit.getPluginManager().callEvent(joinEvent.apply(applied)); + }); + } + + @EventHandler + public void onQuit(PlayerQuitEvent e) { + Player player = e.getPlayer(); + UUID uuid = player.getUniqueId(); + Optional optional = isBlobSerializable(uuid); + if (optional.isEmpty()) + return; + T serializable = optional.get(); + if (quitEvent != null) + Bukkit.getPluginManager().callEvent(quitEvent.apply(serializable)); + saving.add(uuid); + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + crudManager.update(serializable.serializeAllAttributes()); + removeObject(uuid); + saving.remove(uuid); + }); + } + + public void addSaving(UUID uuid) { + saving.add(uuid); + } + + public void removeSaving(UUID uuid) { + saving.remove(uuid); + } + + public void addObject(UUID key, T serializable) { + serializables.put(key, serializable); + } + + public void addObject(Player player, T serializable) { + addObject(player.getUniqueId(), serializable); + } + + public void removeObject(UUID key) { + serializables.remove(key); + } + + public void removeObject(Player player) { + removeObject(player.getUniqueId()); + } + + public Optional isBlobSerializable(UUID uuid) { + return Optional.ofNullable(serializables.get(uuid)); + } + + public Optional isBlobSerializable(Player player) { + return isBlobSerializable(player.getUniqueId()); + } + + public void ifIsOnline(UUID uuid, Consumer consumer) { + Optional optional = isBlobSerializable(uuid); + optional.ifPresent(consumer); + } + + public void ifIsOnlineThenUpdateElse(UUID uuid, Consumer consumer, + Runnable runnable) { + Optional optional = isBlobSerializable(uuid); + boolean isPresent = optional.isPresent(); + if (isPresent) { + T serializable = optional.get(); + consumer.accept(serializable); + crudManager.update(serializable.serializeAllAttributes()); + } else { + runnable.run(); + } + } + + public void ifIsOnlineThenUpdate(UUID uuid, Consumer consumer) { + ifIsOnlineThenUpdateElse(uuid, consumer, () -> { + }); + } + + public CompletableFuture readAsynchronously(String key) { + CompletableFuture future = new CompletableFuture<>(); + Bukkit.getScheduler().runTaskAsynchronously(getPlugin(), () -> + future.complete(generator.apply(crudManager.read(key)))); + return future; + } + + public void readThenUpdate(String key, Consumer consumer) { + T serializable = generator.apply(crudManager.read(key)); + consumer.accept(serializable); + crudManager.update(serializable.serializeAllAttributes()); + } + + public boolean exists(String key) { + return crudManager.exists(key); + } + + private void saveAll() { + serializables.values().forEach(serializable -> crudManager.update(serializable.serializeAllAttributes())); + } + + public Collection getAll() { + return serializables.values(); + } + + public StorageType getStorageType() { + return crudManager.getStorageType(); + } + + public IdentifierType getIdentifierType() { + return crudManager.getIdentifierType(); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/BlobSerializableManagerFactory.java b/src/main/java/us/mytheria/bloblib/entities/BlobSerializableManagerFactory.java new file mode 100644 index 00000000..db6bb7ff --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/BlobSerializableManagerFactory.java @@ -0,0 +1,133 @@ +package us.mytheria.bloblib.entities; + +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import us.mytheria.bloblib.managers.ManagerDirector; + +import java.util.function.Function; + +public class BlobSerializableManagerFactory { + /** + * Creates a simple BlobSerializableManager that does not listen to events + * and doesn't log activity in console regarding the crud operations. + * + * @param managerDirector The manager director + * @param generator The generator function + * @param crudableName The crudable name + * @param The type of the blob serializable + * @return The blob serializable manager + */ + public static BlobSerializableManager SIMPLE(ManagerDirector managerDirector, + Function generator, + String crudableName) { + return SIMPLE(managerDirector, generator, crudableName, false); + } + + /** + * Creates a simple BlobSerializableManager that does not listen to events. + * + * @param managerDirector The manager director + * @param generator The generator function + * @param crudableName The crudable name + * @param logActivity Whether to log activity in console regarding the crud operations + * @param The type of the blob serializable + * @return The blob serializable manager + */ + public static BlobSerializableManager SIMPLE(ManagerDirector managerDirector, + Function generator, + String crudableName, + boolean logActivity) { + return new BlobSerializableManager<>(managerDirector, + crudable -> crudable, generator, crudableName, + logActivity, null, null); + } + + /** + * Creates a simple BlobSerializableManager that allows + * listening to custom events. Does not log activity in console + * regarding the crud operations. + * + * @param managerDirector The manager director + * @param generator The generator function + * @param crudableName The crudable name + * @param joinEvent The join event. + * Function consumes the BlobSerializable + * related in the event and needs to return + * the event to be called. + * @param quitEvent The quit event. + * Function consumes the BlobSerializable + * related in the event and needs to return + * the event to be called. + * @param The type of the blob serializable + * @return The blob serializable manager + */ + public static BlobSerializableManager LISTENER(ManagerDirector managerDirector, + Function generator, + String crudableName, + @Nullable Function joinEvent, + @Nullable Function quitEvent) { + return LISTENER(managerDirector, generator, crudableName, false, joinEvent, quitEvent); + } + + /** + * Creates a simple BlobSerializableManager that allows + * listening to custom events. + * + * @param managerDirector The manager director + * @param generator The generator function + * @param crudableName The crudable name + * @param logActivity Whether to log activity in console regarding the crud operations + * @param joinEvent The join event. + * Function consumes the BlobSerializable + * related in the event and needs to return + * the event to be called. + * @param quitEvent The quit event. + * Function consumes the BlobSerializable + * related in the event and needs to return + * the event to be called. + * @param The type of the blob serializable + * @return The blob serializable manager + */ + public static BlobSerializableManager LISTENER(ManagerDirector managerDirector, + Function generator, + String crudableName, + boolean logActivity, + @Nullable Function joinEvent, + @Nullable Function quitEvent) { + return new BlobSerializableManager<>(managerDirector, + crudable -> crudable, generator, crudableName, + logActivity, joinEvent, quitEvent); + } + + /** + * Creates a complex BlobSerializableManager that allows + * listening to custom events and allows to create a new + * BlobCrudable when the BlobSerializable is not found. + * It also allows passing a newBorn function which autofills + * the BlobCrudable with default values whenever + * a user is found as a new user (never joined before). + * + * @param managerDirector The manager director + * @param newBorn The newborn function. + * @param generator The generator function + * @param crudableName The crudable name + * @param logActivity Whether to log activity in console regarding the crud operations + * @param joinEvent The join event. + * Function consumes the BlobSerializable + * related in the event and needs to return + * @param quitEvent The quit event. + * Function consumes the BlobSerializable + * related in the event and needs to return + * @param The type of the blob serializable + * @return The blob serializable manager + */ + public static BlobSerializableManager COMPLEX(ManagerDirector managerDirector, + Function newBorn, + Function generator, + String crudableName, + boolean logActivity, + @Nullable Function joinEvent, + @Nullable Function quitEvent) { + return new BlobSerializableManager<>(managerDirector, newBorn, generator, crudableName, logActivity, joinEvent, quitEvent); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/BlobSoundReader.java b/src/main/java/us/mytheria/bloblib/entities/BlobSoundReader.java index c642bcff..7ea3494f 100644 --- a/src/main/java/us/mytheria/bloblib/entities/BlobSoundReader.java +++ b/src/main/java/us/mytheria/bloblib/entities/BlobSoundReader.java @@ -3,8 +3,10 @@ import org.bukkit.Sound; import org.bukkit.SoundCategory; import org.bukkit.configuration.ConfigurationSection; -import us.mytheria.bloblib.BlobLibAssetAPI; +import us.mytheria.bloblib.BlobLib; +import us.mytheria.bloblib.api.BlobLibSoundAPI; import us.mytheria.bloblib.entities.message.BlobSound; +import us.mytheria.bloblib.entities.message.MessageAudience; import java.util.Optional; @@ -23,19 +25,46 @@ public static BlobSound read(ConfigurationSection section) { } catch (IllegalArgumentException e) { throw new IllegalArgumentException("Invalid Sound's Category: " + section.getString("Category")); } + MessageAudience audience = MessageAudience.PLAYER; + if (section.contains("Audience")) + try { + audience = MessageAudience.valueOf(section.getString("Audience")); + } catch (IllegalArgumentException e) { + BlobLib.getAnjoLogger().singleError("Invalid Sound's Audience: " + section.getString("Audience")); + } return new BlobSound( Sound.valueOf(section.getString("Sound")), (float) section.getDouble("Volume"), (float) section.getDouble("Pitch"), - category.orElse(null) + category.orElse(null), + audience ); } public static Optional parse(ConfigurationSection parentConfigurationSection) { if (!parentConfigurationSection.contains("BlobSound")) return Optional.empty(); - if (parentConfigurationSection.isString("BlobSound")) - return Optional.ofNullable(BlobLibAssetAPI.getSound(parentConfigurationSection.getString("BlobSound"))); + if (parentConfigurationSection.isString("BlobSound")) { + String sound = parentConfigurationSection.getString("BlobSound"); + String[] split = sound.split(":"); + if (split.length == 1) + return Optional.ofNullable(BlobLibSoundAPI.getInstance().getSound(sound)); + else if (split.length == 2) { + Optional optional = Optional.ofNullable(BlobLibSoundAPI.getInstance().getSound(split[0])); + if (optional.isEmpty()) + return Optional.empty(); + MessageAudience audience; + try { + audience = MessageAudience.valueOf(split[1]); + } catch (IllegalArgumentException e) { + BlobLib.getAnjoLogger().singleError("Invalid Sound's Audience: " + split[1]); + return Optional.empty(); + } + BlobSound blobSound = optional.get(); + return Optional.of(new BlobSound(blobSound.sound(), blobSound.volume(), + blobSound.pitch(), blobSound.soundCategory(), audience)); + } + } return Optional.of(read(parentConfigurationSection.getConfigurationSection("BlobSound"))); } } diff --git a/src/main/java/us/mytheria/bloblib/entities/BukkitPluginOperator.java b/src/main/java/us/mytheria/bloblib/entities/BukkitPluginOperator.java new file mode 100644 index 00000000..693919c4 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/BukkitPluginOperator.java @@ -0,0 +1,164 @@ +package us.mytheria.bloblib.entities; + +import org.bukkit.Bukkit; +import org.bukkit.NamespacedKey; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitTask; +import org.jetbrains.annotations.NotNull; + +/** + * A plugin operator is made to be able to make operations that + * require a Plugin instance. + * It's ideal to make an Asset using an Interface that contains + * this asset's operations and then implement this interface + * in the Asset class. + * Through this way, only the Asset class would be exposed to + * the API user. + */ +public interface BukkitPluginOperator { + static BukkitPluginOperator of(Plugin plugin) { + return () -> plugin; + } + + /** + * Gets the plugin that manages this PluginOperator. + * + * @return the plugin that manages this PluginOperator. + */ + Plugin getPlugin(); + + /** + * Will run a task in the main thread. + * + * @param runnable the task to run + * @return the BukkitTask that is running the task + */ + default BukkitTask runTask(Runnable runnable) { + return Bukkit.getScheduler().runTask(getPlugin(), runnable); + } + + /** + * Will run a task in the main thread after the given delay. + * + * @param runnable the task to run + * @param delay the delay + * @param unit the unit of the delay + * @return the BukkitTask that is running the task + */ + default BukkitTask runTaskLater(Runnable runnable, long delay, MinecraftTimeUnit unit) { + if (unit == MinecraftTimeUnit.TICKS) + return Bukkit.getScheduler().runTaskLater(getPlugin(), runnable, delay); + long x = (long) MinecraftTimeUnit.TICKS.convert(delay, unit); + return Bukkit.getScheduler().runTaskLater(getPlugin(), runnable, x); + } + + /** + * Will run a task in the main thread after the given delay. + * + * @param runnable the task to run + * @param delay the delay in ticks + * @return the BukkitTask that is running the task + */ + default BukkitTask runTaskLater(Runnable runnable, long delay) { + return runTaskLater(runnable, delay, MinecraftTimeUnit.TICKS); + } + + /** + * Will run a task in the main thread after the given delay and repeat it every period. + * + * @param runnable the task to run + * @param delay the delay + * @param period the period + * @param unit the unit of the delay and period + * @return the BukkitTask that is running the task + */ + default BukkitTask runTaskTimer(Runnable runnable, long delay, long period, MinecraftTimeUnit unit) { + if (unit == MinecraftTimeUnit.TICKS) + return Bukkit.getScheduler().runTaskTimer(getPlugin(), runnable, delay, period); + long x = (long) MinecraftTimeUnit.TICKS.convert(delay, unit); + long y = (long) MinecraftTimeUnit.TICKS.convert(period, unit); + return Bukkit.getScheduler().runTaskTimer(getPlugin(), runnable, x, y); + } + + /** + * Will run a task in the main thread after the given delay and repeat it every period. + * + * @param runnable the task to run + * @param delay the delay in ticks + * @param period the period in ticks + * @return the BukkitTask that is running the task + */ + default BukkitTask runTaskTimer(Runnable runnable, long delay, long period) { + return runTaskTimer(runnable, delay, period, MinecraftTimeUnit.TICKS); + } + + /** + * Will run a task in the async thread. + * + * @param runnable the task to run + * @return the BukkitTask that is running the task + */ + default BukkitTask runTaskAsynchronously(Runnable runnable) { + return Bukkit.getScheduler().runTaskAsynchronously(getPlugin(), runnable); + } + + /** + * Will run a task in the async thread after the given delay. + * + * @param runnable the task to run + * @param delay the delay + * @param unit the unit of the delay + * @return the BukkitTask that is running the task + */ + default BukkitTask runTaskLaterAsynchronously(Runnable runnable, long delay, MinecraftTimeUnit unit) { + if (unit == MinecraftTimeUnit.TICKS) + return Bukkit.getScheduler().runTaskLaterAsynchronously(getPlugin(), runnable, delay); + long x = (long) MinecraftTimeUnit.TICKS.convert(delay, unit); + return Bukkit.getScheduler().runTaskLaterAsynchronously(getPlugin(), runnable, x); + } + + /** + * Will run a task in the async thread after the given delay. + * + * @param runnable the task to run + * @param delay the delay in ticks + * @return the BukkitTask that is running the task + */ + default BukkitTask runTaskLaterAsynchronously(Runnable runnable, long delay) { + return runTaskLaterAsynchronously(runnable, delay, MinecraftTimeUnit.TICKS); + } + + /** + * Will run a task in the async thread after the given delay and repeat it every period. + * + * @param runnable the task to run + * @param delay the delay + * @param period the period + * @param unit the unit of the delay and period + * @return the BukkitTask that is running the task + */ + default BukkitTask runTaskTimerAsynchronously(Runnable runnable, long delay, long period, MinecraftTimeUnit unit) { + if (unit == MinecraftTimeUnit.TICKS) + return Bukkit.getScheduler().runTaskTimerAsynchronously(getPlugin(), runnable, delay, period); + long x = (long) MinecraftTimeUnit.TICKS.convert(delay, unit); + long y = (long) MinecraftTimeUnit.TICKS.convert(period, unit); + return Bukkit.getScheduler().runTaskTimerAsynchronously(getPlugin(), runnable, x, y); + } + + /** + * Will run a task in the async thread after the given delay and repeat it every period. + * + * @param runnable the task to run + * @param delay the delay in ticks + * @param period the period in ticks + * @return the BukkitTask that is running the task + */ + default BukkitTask runTaskTimerAsynchronously(Runnable runnable, long delay, long period) { + return runTaskTimerAsynchronously(runnable, delay, period, MinecraftTimeUnit.TICKS); + } + + @NotNull + default NamespacedKey generateNamespacedKey(String key) { + return new NamespacedKey(getPlugin(), key); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/CommandMultiSlotable.java b/src/main/java/us/mytheria/bloblib/entities/CommandMultiSlotable.java deleted file mode 100644 index 33d95a29..00000000 --- a/src/main/java/us/mytheria/bloblib/entities/CommandMultiSlotable.java +++ /dev/null @@ -1,137 +0,0 @@ -package us.mytheria.bloblib.entities; - -import me.anjoismysign.anjo.entities.Result; -import org.bukkit.Bukkit; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import us.mytheria.bloblib.entities.inventory.SuperBlobButtonManager; -import us.mytheria.bloblib.itemstack.ItemStackReader; -import us.mytheria.bloblib.objects.SerializableItem; - -import java.util.HashSet; -import java.util.Set; - -public class CommandMultiSlotable extends MultiSlotable { - private final String key; - private final boolean executesCommand; - private final String command; - - public static CommandMultiSlotable read(ConfigurationSection section, String key) { - ConfigurationSection itemStackSection = section.getConfigurationSection("ItemStack"); - if (itemStackSection == null) { - Bukkit.getLogger().severe("ItemStack section is null for " + key); - return null; - } - ItemStack itemStack = ItemStackReader.read(itemStackSection).build(); - HashSet list = new HashSet<>(); - String read = section.getString("Slot", "-1"); - String[] slots = read.split(","); - if (slots.length != 1) { - for (String slot : slots) { - add(list, slot, section.getName()); - } - } else { - add(list, read, section.getName()); - } - boolean executesCommand = section.getBoolean("ExecutesCommand", false); - String command = section.getString("Command", "say PLEASE CHECK YOUR NEW CONFIG FILES SINCE THERE'S A " + - "FILE THAT'S LOADING A BUTTON WHICH 'ExecutesCommand' is set as true but 'Command' is not specified."); - return new CommandMultiSlotable(list, itemStack, key, executesCommand, command); - } - - public static CommandMultiSlotable fromConfigurationSection(ConfigurationSection section, String key) { - ItemStack itemStack = SerializableItem.fromConfigurationSection(section.getConfigurationSection("ItemStack")); - HashSet list = new HashSet<>(); - String read = section.getString("Slot", "-1"); - String[] slots = read.split(","); - if (slots.length != 1) { - for (String slot : slots) { - add(list, slot, section.getName()); - } - } else { - add(list, read, section.getName()); - } - boolean executesCommand = section.getBoolean("ExecutesCommand", false); - String command = section.getString("Command", "say PLEASE CHECK YOUR NEW CONFIG FILES SINCE THERE'S A " + - "FILE THAT'S LOADING A BUTTON WHICH 'ExecutesCommand' is set as true but 'Command' is not specified."); - return new CommandMultiSlotable(list, itemStack, key, executesCommand, command); - } - - private CommandMultiSlotable(Set slots, ItemStack itemStack, String key, - boolean executesCommand, String command) { - super(slots, itemStack); - this.key = key; - this.executesCommand = executesCommand; - this.command = command; - } - - public void setInInventory(Inventory inventory) { - for (Integer slot : getSlots()) { - inventory.setItem(slot, getItemStack()); - } - } - - public void setInSuperBlobButtonManager(SuperBlobButtonManager superBlobButtonManager) { - superBlobButtonManager.getStringKeys().put(key, this.getSlots()); - if (executesCommand) - superBlobButtonManager.addCommand(key, command); - for (Integer slot : getSlots()) { - superBlobButtonManager.getIntegerKeys().put(slot, getItemStack()); - } - } - - private static void add(Set set, String raw, String sectionName) { - String[] split = raw.split("-"); - switch (split.length) { - case 1 -> { - int slot = Integer.parseInt(split[0]); - if (slot < 0) { - Bukkit.getLogger().info(sectionName + " got a slot that's is smaller than 0."); - Bukkit.getLogger().info("This is not possible in an inventory so it was set"); - Bukkit.getLogger().info("to '0' which is default."); - slot = 0; - } - set.add(slot); - } - case 2 -> { - int start = 0; - try { - start = Integer.parseInt(split[0]); - } catch (NumberFormatException e) { - Bukkit.getLogger().info(sectionName + " got a slot that's not a number"); - Bukkit.getLogger().info("This is not possible in an inventory so it was set"); - Bukkit.getLogger().info("to '0' which is default."); - } - int end = 0; - try { - end = Integer.parseInt(split[1]); - } catch (NumberFormatException e) { - Bukkit.getLogger().info(sectionName + " got a slot that's not a number"); - Bukkit.getLogger().info("This is not possible in an inventory so it was set"); - Bukkit.getLogger().info("to '0' which is default."); - } - for (int i = start; i <= end; i++) { - set.add(i); - } - } - default -> { - Bukkit.getLogger().info("Invalid range inside inside " + sectionName); - Bukkit.getLogger().info("The range must be in the format of 'start-end' or 'number'"); - } - } - } - - public Result getCommand() { - return new Result<>(command, executesCommand); - } - - public String getKey() { - return key; - } - - @Override - public Set getSlots() { - return (Set) super.getSlots(); - } -} diff --git a/src/main/java/us/mytheria/bloblib/entities/ComplexEventListener.java b/src/main/java/us/mytheria/bloblib/entities/ComplexEventListener.java new file mode 100644 index 00000000..a41e107d --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/ComplexEventListener.java @@ -0,0 +1,128 @@ +package us.mytheria.bloblib.entities; + +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.File; +import java.util.*; +import java.util.function.Consumer; + +public class ComplexEventListener { + private final Map configValues; + private final boolean register; + + public static ComplexEventListener of(File file) { + return new ComplexEventListener(YamlConfiguration.loadConfiguration(file)); + } + + public ComplexEventListener(ConfigurationSection section) { + configValues = new HashMap<>(); + if (!section.isBoolean("Register")) + throw new IllegalArgumentException("'Register' must be a boolean"); + register = section.getBoolean("Register"); + ConfigurationSection configSection = section.getConfigurationSection("Values"); + Set keys = configSection.getKeys(true); + for (String key : keys) { + if (configSection.isConfigurationSection(key)) { + configValues.put(key, configSection.getConfigurationSection(key)); + continue; + } + Object value = configSection.get(key); + configValues.put(key, value); + } + } + + public boolean register() { + return register; + } + + public boolean ifRegister(Consumer consumer) { + if (register) { + consumer.accept(this); + return true; + } + return false; + } + + private Object getValue(String key) { + return configValues.get(key); + } + + public int getInt(String key) { + return (int) getValue(key); + } + + public boolean getBoolean(String key) { + return (boolean) getValue(key); + } + + public double getDouble(String key) { + return (double) getValue(key); + } + + public long getLong(String key) { + return (long) getValue(key); + } + + public float getFloat(String key) { + return (float) getValue(key); + } + + public String getString(String key) { + return (String) getValue(key); + } + + public List getIntList(String key) { + List intList = new ArrayList<>(); + List list = (List) getValue(key); + for (Object item : list) { + intList.add((int) item); + } + return intList; + } + + public List getBooleanList(String key) { + List booleanList = new ArrayList<>(); + List list = (List) getValue(key); + for (Object item : list) { + booleanList.add((boolean) item); + } + return booleanList; + } + + public List getDoubleList(String key) { + List doubleList = new ArrayList<>(); + List list = (List) getValue(key); + for (Object item : list) { + doubleList.add((double) item); + } + return doubleList; + } + + public List getLongList(String key) { + List longList = new ArrayList<>(); + List list = (List) getValue(key); + for (Object item : list) { + longList.add((long) item); + } + return longList; + } + + public List getFloatList(String key) { + List floatList = new ArrayList<>(); + List list = (List) getValue(key); + for (Object item : list) { + floatList.add((float) item); + } + return floatList; + } + + @SuppressWarnings("unchecked") + public List getStringList(String key) { + return (List) getValue(key); + } + + public ConfigurationSection getConfigurationSection(String key) { + return (ConfigurationSection) getValue(key); + } +} \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/entities/ConfigDecorator.java b/src/main/java/us/mytheria/bloblib/entities/ConfigDecorator.java new file mode 100644 index 00000000..f11b59e0 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/ConfigDecorator.java @@ -0,0 +1,30 @@ +package us.mytheria.bloblib.entities; + +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import us.mytheria.bloblib.managers.BlobPlugin; + +public class ConfigDecorator { + private final BlobPlugin plugin; + + public ConfigDecorator(BlobPlugin plugin) { + this.plugin = plugin; + } + + public ListenersSection reloadAndGetListeners() { + FileConfiguration root = plugin.getConfig(); + root.options().copyDefaults(true); + ConfigurationSection listeners = root.getConfigurationSection("Listeners"); + if (!listeners.isConfigurationSection("TinyListeners")) + listeners.createSection("TinyListeners"); + if (!listeners.isConfigurationSection("ComplexListeners")) + listeners.createSection("ComplexListeners"); + if (!listeners.isConfigurationSection("SimpleListeners")) + listeners.createSection("SimpleListeners"); + ConfigurationSection tinyListeners = listeners.getConfigurationSection("TinyListeners"); + ConfigurationSection complexListeners = listeners.getConfigurationSection("ComplexListeners"); + ConfigurationSection simpleListeners = listeners.getConfigurationSection("SimpleListeners"); + plugin.saveConfig(); + return new ListenersSection(tinyListeners, complexListeners, simpleListeners, root); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/DocumentDecorator.java b/src/main/java/us/mytheria/bloblib/entities/DocumentDecorator.java new file mode 100644 index 00000000..5968a961 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/DocumentDecorator.java @@ -0,0 +1,268 @@ +package us.mytheria.bloblib.entities; + +import org.bson.Document; +import org.bson.types.Binary; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +public record DocumentDecorator(Document document) { + + @NotNull + public static DocumentDecorator deserialize(byte[] bytes) { + DocumentDecorator decorator = new DocumentDecorator(Document.parse(new String(bytes))); + return Objects.requireNonNull(decorator, "Failed to deserialize document"); + } + + /** + * Will get the document. + * + * @return the document + */ + public Document call() { + return document; + } + + /** + * Will get an object from the document. + * + * @param key the key + * @return the object + */ + public Optional has(String key) { + return Optional.ofNullable(document.get(key)); + } + + /** + * Will get a long from the document. + * + * @param key the key + * @return the long + */ + public Optional hasLong(String key) { + return Optional.ofNullable(document.getLong(key)); + } + + /** + * Will get a float from the document. + * + * @param key the key + * @return the float + */ + public Optional hasFloat(String key) { + Float value = document.get(key, Float.class); + if (value == null) + return Optional.empty(); + return Optional.ofNullable(value); + } + + /** + * Will get a double from the document. + * + * @param key the key + * @return the double + */ + public Optional hasDouble(String key) { + return Optional.ofNullable(document.getDouble(key)); + } + + /** + * Will get an integer from the document. + * + * @param key the key + * @return the integer + */ + public Optional hasInteger(String key) { + return Optional.ofNullable(document.getInteger(key)); + } + + /** + * Will get a short from the document. + * + * @param key the key + * @return the short + */ + public Optional hasShort(String key) { + Short value = document.get(key, Short.class); + if (value == null) + return Optional.empty(); + return Optional.ofNullable(value); + } + + /** + * Will get a byte from the document. + * + * @param key the key + * @return the byte + */ + public Optional hasByte(String key) { + Byte value = document.get(key, Byte.class); + if (value == null) + return Optional.empty(); + return Optional.ofNullable(value); + } + + /** + * Will get a string from the document. + * + * @param key the key + * @return the string + */ + public Optional hasString(String key) { + return Optional.ofNullable(document.getString(key)); + } + + /** + * Will get a character from the document. + * + * @param key the key + * @return the character + */ + public Optional hasCharacter(String key) { + Character value = document.get(key, Character.class); + if (value == null) + return Optional.empty(); + return Optional.ofNullable(value); + } + + /** + * Will get a boolean from the document. + * + * @param key the key + * @return the boolean + */ + public Optional hasBoolean(String key) { + return Optional.ofNullable(document.getBoolean(key)); + } + + /** + * Will get a long list from the document. + * + * @param key the key + * @return the long list + */ + public Optional> hasLongList(String key) { + return Optional.ofNullable(document.getList(key, Long.class)); + } + + /** + * Will get a float list from the document. + * + * @param key the key + * @return the float list + */ + public Optional> hasFloatList(String key) { + return Optional.ofNullable(document.getList(key, Float.class)); + } + + /** + * Will get a double list from the document. + * + * @param key the key + * @return the double list + */ + public Optional> hasDoubleList(String key) { + return Optional.ofNullable(document.getList(key, Double.class)); + } + + /** + * Will get an integer list from the document. + * + * @param key the key + * @return the integer list + */ + public Optional> hasIntegerList(String key) { + return Optional.ofNullable(document.getList(key, Integer.class)); + } + + /** + * Will get a short list from the document. + * + * @param key the key + * @return the short list + */ + public Optional> hasShortList(String key) { + return Optional.ofNullable(document.getList(key, Short.class)); + } + + /** + * Will get a byte list from the document. + * + * @param key the key + * @return the byte list + */ + public Optional> hasByteList(String key) { + return Optional.ofNullable(document.getList(key, Byte.class)); + } + + /** + * Will get a string list from the document. + * + * @param key the key + * @return the string list + */ + public Optional> hasStringList(String key) { + return Optional.ofNullable(document.getList(key, String.class)); + } + + /** + * Will get a character list from the document. + * + * @param key the key + * @return the character list + */ + public Optional> hasCharacterList(String key) { + return Optional.ofNullable(document.getList(key, Character.class)); + } + + /** + * Will get a boolean list from the document. + * + * @param key the key + * @return the boolean list + */ + public Optional> hasBooleanList(String key) { + return Optional.ofNullable(document.getList(key, Boolean.class)); + } + + /** + * Will get a byte array from the document. + * It's expected that the byte array was stored + * through {@link #serializeByteArray(byte[], String)} + * + * @param key the key + * @return the byte array + */ + public byte @Nullable [] getByteArray(String key) { + try { + Binary byteArray = (Binary) document.get(key); + return byteArray.getData(); + } catch (Exception e) { + return null; + } + } + + /** + * Will serliaize a byte array into the document + * + * @param byteArray the byte array + * @param key the key + */ + void serializeByteArray(byte[] byteArray, String key) { + document.put(key, new Binary(byteArray)); + } + + /** + * Will serialize the document into a byte array + * which can later be deserialized through + * {@link #deserialize(byte[])} + * + * @return the byte array + */ + public byte @NotNull [] serialize() { + return document.toJson().getBytes(); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/Fuel.java b/src/main/java/us/mytheria/bloblib/entities/Fuel.java new file mode 100644 index 00000000..7126be15 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/Fuel.java @@ -0,0 +1,60 @@ +package us.mytheria.bloblib.entities; + +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +public class Fuel { + @Nullable + private final ItemStack replacement; + private final long burnTime; + + /** + * Creates a new Fuel object with a replacement. + * + * @param replacement The item that will replace the fuel item. + * @param burnTime The burn time of the item. + * @return The new Fuel object. + */ + public static Fuel withReplacement(@NotNull ItemStack replacement, long burnTime) { + return new Fuel(Objects.requireNonNull(replacement), burnTime); + } + + /** + * Creates a new Fuel object without a replacement. + * + * @param burnTime The burn time of the item. + * @return The new Fuel object. + */ + public static Fuel of(long burnTime) { + return new Fuel(null, burnTime); + } + + private Fuel(@Nullable ItemStack replacement, long burnTime) { + this.replacement = replacement; + this.burnTime = burnTime; + } + + /** + * Gets the replacement item. + * If not null, this item should replace + * the fuel item whenever done the smelt operation. + * + * @return The replacement item. + */ + @Nullable + public ItemStack getReplacement() { + return replacement; + } + + /** + * Gets the burn time of the item. + * + * @return The burn time of the item. + */ + public long getBurnTime() { + return burnTime; + } +} \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/entities/FurnaceOperation.java b/src/main/java/us/mytheria/bloblib/entities/FurnaceOperation.java new file mode 100644 index 00000000..1babcd08 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/FurnaceOperation.java @@ -0,0 +1,70 @@ +package us.mytheria.bloblib.entities; + +import org.bukkit.Bukkit; +import org.bukkit.inventory.FurnaceRecipe; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.Recipe; +import org.jetbrains.annotations.Nullable; + +public record FurnaceOperation(boolean success, @Nullable ItemStack result, + float experience, int amount) { + + /** + * Creates a new failed furnace operation. + * + * @return The failed furnace operation + */ + public static FurnaceOperation fail() { + return new FurnaceOperation(false, null, + 0F, 0); + } + + /** + * Creates a new furnace operation with the given result. + * It will be considered a success and the passed ItemStack + * is the result of the operation. + * + * @param result The result of the operation + * @return The furnace operation + */ + public static FurnaceOperation of(ItemStack result, + float experience, + int amount) { + return new FurnaceOperation(true, result, + experience, amount); + } + + /** + * Get the result of a vanilla furnace recipe passing + * input as the smelting input ingredient. + * If no recipe is found, it will return a failed operation. + * Otherwise, it will return a successful operation with + * the result of the recipe. + * + * @param input The input ingredient + * @return The result of the vanilla furnace recipe + */ + public static FurnaceOperation vanilla(ItemStack input) { + FurnaceRecipe recipe = getFurnaceRecipe(input); + if (recipe == null) { + return fail(); + } + return of(recipe.getResult(), + recipe.getExperience(), + input.getAmount()); + } + + private static FurnaceRecipe getFurnaceRecipe(ItemStack input) { + for (Recipe recipe : Bukkit.getServer().getRecipesFor(input)) { + if (recipe instanceof FurnaceRecipe furnaceRecipe) { + return furnaceRecipe; + } + } + return null; + } + + @Override + public float experience() { + return experience; + } +} \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/entities/GenericManager.java b/src/main/java/us/mytheria/bloblib/entities/GenericManager.java new file mode 100644 index 00000000..e06efe9e --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/GenericManager.java @@ -0,0 +1,28 @@ +package us.mytheria.bloblib.entities; + +import us.mytheria.bloblib.managers.BlobPlugin; +import us.mytheria.bloblib.managers.Manager; + +public abstract class GenericManager> extends Manager { + /** + * managerDirector reference is stored for the reason + * of not wanting to cast whenever using getter (should improve a little bit + * of performance while sacrificing a little bit of memory). + */ + private final D managerDirector; + + public GenericManager(D managerDirector) { + super(managerDirector); + this.managerDirector = managerDirector; + } + + @Override + public D getManagerDirector() { + return managerDirector; + } + + @Override + public T getPlugin() { + return managerDirector.getPlugin(); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/GenericManagerDirector.java b/src/main/java/us/mytheria/bloblib/entities/GenericManagerDirector.java new file mode 100644 index 00000000..29afb58e --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/GenericManagerDirector.java @@ -0,0 +1,23 @@ +package us.mytheria.bloblib.entities; + +import us.mytheria.bloblib.managers.BlobPlugin; +import us.mytheria.bloblib.managers.ManagerDirector; + +public abstract class GenericManagerDirector extends ManagerDirector { + /** + * plugin reference is stored for the reason + * of not wanting to cast whenever using getter (should improve a little bit + * of performance while sacrificing a little bit of memory). + */ + private final T plugin; + + public GenericManagerDirector(T plugin) { + super(plugin); + this.plugin = plugin; + } + + @Override + public T getPlugin() { + return plugin; + } +} \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/entities/GitHubPluginUpdater.java b/src/main/java/us/mytheria/bloblib/entities/GitHubPluginUpdater.java new file mode 100644 index 00000000..9dcad047 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/GitHubPluginUpdater.java @@ -0,0 +1,190 @@ +package us.mytheria.bloblib.entities; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; +import us.mytheria.bloblib.BlobLib; +import us.mytheria.bloblib.api.BlobLibMessageAPI; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.ProtocolException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; + +public class GitHubPluginUpdater implements PluginUpdater { + private final JavaPlugin plugin; + private final String currentVersion, pluginName, author, repository; + private boolean updateAvailable; + private final UpdaterListener listener; + private String latestVersion; + + public GitHubPluginUpdater(JavaPlugin plugin, + String repositoryOwner, String repository) { + this.plugin = plugin; + this.author = repositoryOwner; + this.repository = repository; + PluginDescriptionFile description = plugin.getDescription(); + this.pluginName = description.getName(); + this.currentVersion = description.getVersion(); + this.listener = new UpdaterListener(plugin, pluginName, this); + this.updateAvailable = false; + reload(); + } + + public void reload() { + ReleaseFetch fetch = fetchLast(); + if (!fetch.isValid()) + return; + this.latestVersion = fetch.latestVersion(); + updateAvailable = !isLatestVersion(); + listener.reload(updateAvailable); + } + + public boolean hasAvailableUpdate() { + return updateAvailable; + } + + public String getLatestVersion() { + return latestVersion; + } + + private boolean isLatestVersion() { + return currentVersion.equals(latestVersion); + } + + public boolean download() { + if (!updateAvailable) + return false; + URL url; + try { + url = new URL(fetchLast().latestUrl()); + } catch (MalformedURLException e) { + BlobLib.getAnjoLogger().error("Could not download latest version of BlobLib because " + + "the URL was malformed"); + return false; + } + Path existentPath = Path.of("plugins", pluginName + "-" + currentVersion + ".jar"); + try { + Files.deleteIfExists(existentPath); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + Path targetPath = Path.of("plugins", pluginName + "-" + latestVersion + ".jar"); + try (InputStream inputStream = url.openStream()) { + Files.copy(inputStream, targetPath); + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + public JavaPlugin getPlugin() { + return plugin; + } + + @NotNull + private ReleaseFetch fetchLast() { + String repoUrl = "https://api.github.com/repos/" + author + "/" + repository + + "/releases"; + URL url; + try { + url = new URL(repoUrl); + } catch (MalformedURLException e) { + e.printStackTrace(); + return ReleaseFetch.INVALID(); + } + HttpURLConnection connection; + try { + connection = (HttpURLConnection) url.openConnection(); + } catch (IOException e) { + plugin.getLogger().severe("Could not connect to GitHub to check for updates"); + return ReleaseFetch.INVALID(); + } + try { + connection.setRequestMethod("GET"); + } catch (ProtocolException e) { + e.printStackTrace(); + return ReleaseFetch.INVALID(); + } + BufferedReader reader; + try { + reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + } catch (IOException e) { + plugin.getLogger().severe("Repository does not exist or is not visible"); + return ReleaseFetch.INVALID(); + } + StringBuilder response = new StringBuilder(); + String line; + try { + while ((line = reader.readLine()) != null) { + response.append(line); + } + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + return ReleaseFetch.INVALID(); + } + + Gson gson = new Gson(); + JsonArray releases = gson.fromJson(response.toString(), JsonArray.class); + JsonObject latestRelease = releases.get(0).getAsJsonObject(); + String latestVersion = latestRelease.get("tag_name").getAsString(); + if (latestVersion.startsWith("v")) + latestVersion = latestVersion.substring(1); + String latestUrl = latestRelease.get("assets").getAsJsonArray().get(0).getAsJsonObject().get("browser_download_url").getAsString(); + return new ReleaseFetch(latestVersion, latestUrl); + } + + private record ReleaseFetch(String latestVersion, String latestUrl) { + private static ReleaseFetch INVALID() { + return new ReleaseFetch(null, null); + } + + public boolean isValid() { + return latestVersion != null && latestUrl != null; + } + } + + private record UpdaterListener(JavaPlugin plugin, String pluginName, PluginUpdater updater) implements Listener { + private void reload(boolean updateAvailable) { + HandlerList.unregisterAll(this); + if (updateAvailable) + Bukkit.getPluginManager().registerEvents(this, plugin); + } + + @EventHandler + public void handle(PlayerJoinEvent event) { + Player player = event.getPlayer(); + if (!player.hasPermission(pluginName.toLowerCase() + ".admin")) + return; + Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> { + if (player == null || !player.isOnline()) + return; + BlobLibMessageAPI.getInstance().getMessage("BlobLib.Updater-Available") + .modder() + .replace("%randomColor%", BlobLib.getInstance().getColorManager().randomColor().toString()) + .replace("%plugin%", plugin.getName()) + .replace("%version%", updater.getLatestVersion()) + .get() + .handle(player); + }, 30L); + } + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/IFileManager.java b/src/main/java/us/mytheria/bloblib/entities/IFileManager.java new file mode 100644 index 00000000..d1b50b02 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/IFileManager.java @@ -0,0 +1,15 @@ +package us.mytheria.bloblib.entities; + +import java.io.File; + +public interface IFileManager { + File messagesDirectory(); + + File soundsDirectory(); + + File inventoriesDirectory(); + + File metaInventoriesDirectory(); + + File actionsDirectory(); +} diff --git a/src/main/java/us/mytheria/bloblib/entities/InventoryButton.java b/src/main/java/us/mytheria/bloblib/entities/InventoryButton.java deleted file mode 100644 index c9145897..00000000 --- a/src/main/java/us/mytheria/bloblib/entities/InventoryButton.java +++ /dev/null @@ -1,21 +0,0 @@ -package us.mytheria.bloblib.entities; - -import java.util.Set; - -public class InventoryButton { - private final String key; - private final Set slots; - - public InventoryButton(String key, Set slots) { - this.key = key; - this.slots = slots; - } - - public String getKey() { - return key; - } - - public Set getSlots() { - return slots; - } -} diff --git a/src/main/java/us/mytheria/bloblib/entities/JOMLReader.java b/src/main/java/us/mytheria/bloblib/entities/JOMLReader.java new file mode 100644 index 00000000..87adcc48 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/JOMLReader.java @@ -0,0 +1,65 @@ +package us.mytheria.bloblib.entities; + +import org.bukkit.configuration.ConfigurationSection; +import org.joml.AxisAngle4f; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +public class JOMLReader { + + public static Vector3f READ_VECTOR3F(ConfigurationSection section, String name) { + if (!section.isConfigurationSection(name)) + throw new IllegalArgumentException(name + " is not valid"); + ConfigurationSection vector = section.getConfigurationSection(name); + if (!vector.isDouble("X")) + throw new IllegalArgumentException(name + ".X is not valid"); + if (!vector.isDouble("Y")) + throw new IllegalArgumentException(name + ".Y is not valid"); + if (!vector.isDouble("Z")) + throw new IllegalArgumentException(name + ".Z is not valid"); + return new Vector3f( + (float) vector.getDouble("X"), + (float) vector.getDouble("Y"), + (float) vector.getDouble("Z")); + } + + public static Quaternionf READ_QUATERNIONF(ConfigurationSection section, String name) { + if (!section.isConfigurationSection(name) && !section.isConfigurationSection(name + "-Quaternion")) + throw new IllegalArgumentException(name + " is not valid"); + Quaternionf quaternionf; + if (section.isConfigurationSection(name)) { + ConfigurationSection angle = section.getConfigurationSection(name); + if (!angle.isDouble("X")) + throw new IllegalArgumentException(name + ".X is not valid"); + if (!angle.isDouble("Y")) + throw new IllegalArgumentException(name + ".Y is not valid"); + if (!angle.isDouble("Z")) + throw new IllegalArgumentException(name + ".Z is not valid"); + if (!angle.isDouble("Angle")) + throw new IllegalArgumentException(name + ".Angle is not valid"); + AxisAngle4f angle4f = new AxisAngle4f( + (float) angle.getDouble("Angle"), + (float) angle.getDouble("X"), + (float) angle.getDouble("Y"), + (float) angle.getDouble("Z") + ); + quaternionf = new Quaternionf(angle4f); + } else { + ConfigurationSection quaternion = section.getConfigurationSection(name + "-Quaternion"); + if (!quaternion.isDouble("X")) + throw new IllegalArgumentException(name + "-Quaternion.X is not valid"); + if (!quaternion.isDouble("Y")) + throw new IllegalArgumentException(name + "-Quaternion.Y is not valid"); + if (!quaternion.isDouble("Z")) + throw new IllegalArgumentException(name + "-Quaternion.Z is not valid"); + if (!quaternion.isDouble("W")) + throw new IllegalArgumentException(name + "-Quaternion.W is not valid"); + quaternionf = new Quaternionf( + quaternion.getDouble("X"), + quaternion.getDouble("Y"), + quaternion.getDouble("Z"), + quaternion.getDouble("W")); + } + return quaternionf; + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/ListenerManager.java b/src/main/java/us/mytheria/bloblib/entities/ListenerManager.java new file mode 100644 index 00000000..e01693d8 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/ListenerManager.java @@ -0,0 +1,32 @@ +package us.mytheria.bloblib.entities; + +import us.mytheria.bloblib.managers.Manager; +import us.mytheria.bloblib.managers.ManagerDirector; + +import java.util.HashSet; +import java.util.Set; + +public abstract class ListenerManager extends Manager { + private final Set listeners; + + public ListenerManager(ManagerDirector managerDirector) { + super(managerDirector); + listeners = new HashSet<>(); + } + + public void reload() { + listeners.forEach(BlobListener::reload); + } + + public void add(BlobListener... listeners) { + for (BlobListener listener : listeners) { + add(listener); + } + } + + public void remove(BlobListener... listeners) { + for (BlobListener listener : listeners) { + remove(listener); + } + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/ListenersSection.java b/src/main/java/us/mytheria/bloblib/entities/ListenersSection.java new file mode 100644 index 00000000..e566cff1 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/ListenersSection.java @@ -0,0 +1,84 @@ +package us.mytheria.bloblib.entities; + +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; + +import java.util.List; + +/** + * @param tinyListeners Tiny listeners section. + * @param complexListeners Complex listeners section. + * @param simpleListeners Simple listeners section. + * @param root Root configuration section. + */ +public record ListenersSection(ConfigurationSection tinyListeners, + ConfigurationSection complexListeners, + ConfigurationSection simpleListeners, + FileConfiguration root) { + public TinyEventListener tinyEventListener(String name) { + return TinyEventListener.READ(tinyListeners, name); + } + + public ComplexEventListener complexEventListener(String name) { + ConfigurationSection section = complexListeners.getConfigurationSection(name); + if (section == null) + section = complexListeners.createSection(name); + return new ComplexEventListener(section); + } + + public SimpleEventListener simpleEventListenerString(String name) { + return SimpleEventListener.STRING(simpleListeners, name); + } + + public SimpleEventListener simpleEventListenerInteger(String name) { + return SimpleEventListener.INTEGER(simpleListeners, name); + } + + public SimpleEventListener simpleEventListenerLong(String name) { + return SimpleEventListener.LONG(simpleListeners, name); + } + + public SimpleEventListener simpleEventListenerDouble(String name) { + return SimpleEventListener.DOUBLE(simpleListeners, name); + } + + public SimpleEventListener simpleEventListenerBoolean(String name) { + return SimpleEventListener.BOOLEAN(simpleListeners, name); + } + + public SimpleEventListener> simpleEventListenerStringList(String name) { + return SimpleEventListener.STRING_LIST(simpleListeners, name); + } + + public SimpleEventListener> simpleEventListenerByteList(String name) { + return SimpleEventListener.BYTE_LIST(simpleListeners, name); + } + + public SimpleEventListener> simpleEventListenerShortList(String name) { + return SimpleEventListener.SHORT_LIST(simpleListeners, name); + } + + public SimpleEventListener> simpleEventListenerIntegerList(String name) { + return SimpleEventListener.INTEGER_LIST(simpleListeners, name); + } + + public SimpleEventListener> simpleEventListenerLongList(String name) { + return SimpleEventListener.LONG_LIST(simpleListeners, name); + } + + public SimpleEventListener> simpleEventListenerFloatList(String name) { + return SimpleEventListener.FLOAT_LIST(simpleListeners, name); + } + + public SimpleEventListener> simpleEventListenerDoubleList(String name) { + return SimpleEventListener.DOUBLE_LIST(simpleListeners, name); + } + + public SimpleEventListener> simpleEventListenerBooleanList(String name) { + return SimpleEventListener.BOOLEAN_LIST(simpleListeners, name); + } + + public SimpleEventListener> simpleEventListenerCharacterList(String name) { + return SimpleEventListener.CHARACTER_LIST(simpleListeners, name); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/MinecraftTimeUnit.java b/src/main/java/us/mytheria/bloblib/entities/MinecraftTimeUnit.java new file mode 100644 index 00000000..f249ba29 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/MinecraftTimeUnit.java @@ -0,0 +1,36 @@ +package us.mytheria.bloblib.entities; + +public enum MinecraftTimeUnit { + NANOSECONDS(1L), + MICROSECONDS(1000L), + MILLISECONDS(1000000L), + TICKS(50000000L), + SECONDS(1000000000L), + MINUTES(60000000000L), + HOURS(3600000000000L), + DAYS(86400000000000L); + private final long scale; + + MinecraftTimeUnit(long scale) { + this.scale = scale; + } + + /** + * Will convert the given duration in the given unit to this unit. + * For example, to convert 4 ticks to milliseconds, use: + *
 {@code
+     * long ticks = MinecraftTimeUnit.MILLISECONDS.convert(4, MinecraftTimeUnit.TICKS);
+     * }
+ * + * @param sourceDuration the source duration + * @param timeUnit the source unit of the duration + * @return the converted duration + */ + public double convert(long sourceDuration, MinecraftTimeUnit timeUnit) { + if (timeUnit.scale < this.scale) { + return (double) sourceDuration / ((double) this.scale / timeUnit.scale); + } else { + return ((double) timeUnit.scale / this.scale) * sourceDuration; + } + } +} \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/entities/MultiSlotable.java b/src/main/java/us/mytheria/bloblib/entities/MultiSlotable.java deleted file mode 100644 index 6c52a8de..00000000 --- a/src/main/java/us/mytheria/bloblib/entities/MultiSlotable.java +++ /dev/null @@ -1,43 +0,0 @@ -package us.mytheria.bloblib.entities; - -import org.bukkit.inventory.ItemStack; - -import java.util.Collection; - -/** - * @author anjoismysign - *

- * A MultiSlotable is a concept of an ItemStack which can - * be placed in multiple slots, lets say slots of an - * inventory/GUI. - */ -public abstract class MultiSlotable { - private final Collection slots; - private final ItemStack itemStack; - - /** - * @param slots The slots to be used. - * @param slots The slots to be used. - * @param itemStack The ItemStack to be used. - */ - public MultiSlotable(Collection slots, ItemStack itemStack) { - this.slots = slots; - this.itemStack = itemStack; - } - - /** - * Retrieves the slots of which this MultiSlotable is linked to. - * - * @return The slots of which this MultiSlotable is linked to. - */ - public Collection getSlots() { - return slots; - } - - /** - * @return The ItemStack of this MultiSlotable. - */ - public ItemStack getItemStack() { - return itemStack; - } -} diff --git a/src/main/java/us/mytheria/bloblib/entities/ObjectBuilderManager.java b/src/main/java/us/mytheria/bloblib/entities/ObjectBuilderManager.java index 0102aead..2a43eeb4 100644 --- a/src/main/java/us/mytheria/bloblib/entities/ObjectBuilderManager.java +++ b/src/main/java/us/mytheria/bloblib/entities/ObjectBuilderManager.java @@ -1,12 +1,14 @@ package us.mytheria.bloblib.entities; import org.bukkit.entity.Player; -import us.mytheria.bloblib.BlobLibAssetAPI; +import org.jetbrains.annotations.NotNull; +import us.mytheria.bloblib.api.BlobLibInventoryAPI; import us.mytheria.bloblib.entities.inventory.BlobInventory; import us.mytheria.bloblib.entities.inventory.ObjectBuilder; import us.mytheria.bloblib.managers.*; import java.util.HashMap; +import java.util.Objects; import java.util.UUID; import java.util.function.BiFunction; @@ -25,19 +27,16 @@ public class ObjectBuilderManager extends Manager { public ObjectBuilderManager(ManagerDirector managerDirector, String fileKey, ObjectDirector objectDirector) { super(managerDirector); - this.objectDirector = objectDirector; - this.fileKey = fileKey; - update(); - } - - @Override - public void loadInConstructor() { selPosListenerManager = getManagerDirector().getPositionListenerManager(); chatManager = getManagerDirector().getChatListenerManager(); dropListenerManager = getManagerDirector().getDropListenerManager(); selectorListenerManager = getManagerDirector().getSelectorManager(); + this.objectDirector = Objects.requireNonNull(objectDirector, "Object director cannot be null."); + this.fileKey = Objects.requireNonNull(fileKey, "File key cannot be null."); + update(); } + @NotNull public ObjectDirector getObjectDirector() { return objectDirector; } @@ -49,12 +48,13 @@ public void reload() { public void update() { this.builders = new HashMap<>(); - BlobInventory inventory = BlobLibAssetAPI.getBlobInventory(fileKey); + BlobInventory inventory = BlobLibInventoryAPI.getInstance().getBlobInventory(fileKey); if (inventory == null) throw new RuntimeException("Inventory file '" + fileKey + "' not found."); this.title = inventory.getTitle(); } + @NotNull public ObjectBuilder getOrDefault(UUID uuid) { ObjectBuilder objectBuilder = builders.get(uuid); if (objectBuilder == null) { @@ -64,51 +64,62 @@ public ObjectBuilder getOrDefault(UUID uuid) { return objectBuilder; } + @NotNull public ObjectBuilder getOrDefault(Player player) { return getOrDefault(player.getUniqueId()); } + @NotNull public ObjectBuilderManager addBuilder(UUID uuid, ObjectBuilder builder) { builders.put(uuid, builder); return this; } + @NotNull public ObjectBuilderManager addBuilder(Player player, ObjectBuilder builder) { addBuilder(player.getUniqueId(), builder); return this; } + @NotNull public ObjectBuilderManager removeBuilder(UUID uuid) { builders.remove(uuid); return this; } + @NotNull public ObjectBuilderManager removeBuilder(Player player) { removeBuilder(player.getUniqueId()); return this; } + @NotNull public ObjectBuilderManager setBuilderBiFunction(BiFunction, ObjectBuilder> function) { builderBiFunction = function; return this; } + @NotNull public String getFileKey() { return fileKey; } + @NotNull public DropListenerManager getDropListenerManager() { return dropListenerManager; } + @NotNull public ChatListenerManager getChatManager() { return chatManager; } + @NotNull public SelectorListenerManager getSelectorListenerManager() { return selectorListenerManager; } + @NotNull public SelPosListenerManager getSelPosListenerManager() { return selPosListenerManager; } diff --git a/src/main/java/us/mytheria/bloblib/entities/ObjectDirector.java b/src/main/java/us/mytheria/bloblib/entities/ObjectDirector.java index ff75b3dd..ed843ae2 100644 --- a/src/main/java/us/mytheria/bloblib/entities/ObjectDirector.java +++ b/src/main/java/us/mytheria/bloblib/entities/ObjectDirector.java @@ -1,6 +1,7 @@ package us.mytheria.bloblib.entities; import me.anjoismysign.anjo.entities.Result; +import org.apache.commons.io.FileUtils; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.entity.Player; @@ -8,7 +9,8 @@ import org.bukkit.event.Listener; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.ItemStack; -import us.mytheria.bloblib.BlobLibAssetAPI; +import org.jetbrains.annotations.NotNull; +import us.mytheria.bloblib.api.BlobLibMessageAPI; import us.mytheria.bloblib.entities.inventory.ObjectBuilder; import us.mytheria.bloblib.itemstack.ItemStackBuilder; import us.mytheria.bloblib.managers.Manager; @@ -16,12 +18,12 @@ import us.mytheria.bloblib.utilities.ItemStackUtil; import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Optional; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import java.util.function.Function; +import java.util.logging.Level; public class ObjectDirector extends Manager implements Listener { private final ObjectBuilderManager objectBuilderManager; @@ -32,9 +34,11 @@ public class ObjectDirector extends Manager implements Lis private final List> adminChildCommands; private final List>> nonAdminChildTabCompleter; private final List>> adminChildTabCompleter; - private final String objectName; + protected final String objectName; private final boolean hasObjectBuilderManager; private boolean objectIsEditable; + protected CompletableFuture loadFilesFuture; + private final Consumer addConsumer; public ObjectDirector(ManagerDirector managerDirector, ObjectDirectorData objectDirectorData, @@ -48,33 +52,49 @@ public ObjectDirector(ManagerDirector managerDirector, super(managerDirector); objectIsEditable = false; this.hasObjectBuilderManager = hasObjectBuilderManager; - if (hasObjectBuilderManager) + if (hasObjectBuilderManager) { this.objectBuilderManager = new ObjectBuilderManager<>(managerDirector, objectDirectorData.objectBuilderKey(), this); - else + this.addConsumer = objectBuilderManager::getOrDefault; + } else { this.objectBuilderManager = null; - Optional loadFilesDirectory = managerDirector.getFileManager().searchFile(objectDirectorData.objectDirectory()); - if (loadFilesDirectory.isEmpty()) + this.addConsumer = player -> { + }; + } + Optional loadFilesDirectory = managerDirector.getRealFileManager().searchFile(objectDirectorData.objectDirectory()); + if (loadFilesDirectory.isEmpty()) { + Bukkit.getLogger().info("The loadFilesPathKey is not valid"); throw new IllegalArgumentException("The loadFilesPathKey is not valid"); + } this.objectManager = new ObjectManager<>(managerDirector, loadFilesDirectory.get(), - HashMap::new, HashMap::new) { - @Override - public void loadFiles(File path) { + ConcurrentHashMap::new, ConcurrentHashMap::new, this) { + public void loadFiles(File path, CompletableFuture mainFuture) { if (!path.exists()) path.mkdir(); - File[] listOfFiles = path.listFiles(); - for (File file : listOfFiles) { - if (file.getName().equals(".DS_Store")) - continue; - if (file.isFile()) { - T blobObject = readFunction.apply(file); - if (blobObject.edit() != null) - objectIsEditable = true; - addObject(blobObject.getKey(), blobObject, file); - } - if (file.isDirectory()) - loadFiles(file); - } + Bukkit.getScheduler().runTaskAsynchronously(getPlugin(), () -> { + String[] extensions = {"yml"}; + Collection files = FileUtils.listFiles(path, extensions, true); + List> futures = new ArrayList<>(); + files.forEach(file -> { + CompletableFuture fileFuture = CompletableFuture.runAsync(() -> { + T blobObject; + try { + blobObject = readFunction.apply(file); + if (blobObject != null) { + if (blobObject.edit() != null) + objectIsEditable = true; + this.addObject(blobObject.getKey(), blobObject, file); + } + } catch (Exception exception) { + Bukkit.getLogger().log(Level.SEVERE, exception.getMessage() + " \n " + + "At: " + file.getPath(), exception); + mainFuture.completeExceptionally(exception); + } + }); + futures.add(fileFuture); + }); + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenAccept(v -> mainFuture.complete(null)); + }); } }; clickEventConsumer = e -> { @@ -319,7 +339,7 @@ private void setDefaultTabCompleter() { if (childTabCompletion != null && !childTabCompletion.isEmpty()) suggestions.addAll(childTabCompletion); } - if (!executor.hasAdminPermission(sender)) + if (!executor.hasAdminPermission(sender, null)) return suggestions; for (Function> childTabCompleter : adminChildTabCompleter) { List childTabCompletion = childTabCompleter.apply(new ExecutorData(executor, args, sender)); @@ -345,11 +365,11 @@ private void setDefaultTabCompleter() { * @param player the player who is removing the object. */ public void removeObject(Player player) { - removeObject(player, key -> { + removeObject(player, object -> { + String key = object.getKey(); ItemStackBuilder builder = ItemStackBuilder.build(Material.COMMAND_BLOCK); builder.displayName(key); builder.lore(); - Object object = getObjectManager().getObject(key); if (ItemStack.class.isInstance(object.getClass())) { ItemStack itemStack = (ItemStack) object; builder.displayName(ItemStackUtil.display(itemStack)); @@ -366,14 +386,15 @@ public void removeObject(Player player) { * @param function the function that is used to get the ItemStack to display * the object to be removed. */ - public void removeObject(Player player, Function function) { - BlobEditor editor = objectManager.makeEditor(player, objectName); - editor.removeElement(player, key -> { + public void removeObject(Player player, Function function) { + BlobEditor editor = objectManager.makeEditor(player); + editor.removeElement(player, element -> { + String key = element.getKey(); player.closeInventory(); getObjectManager().removeObject(key); - BlobLibAssetAPI.getMessage("Editor.Removed") + BlobLibMessageAPI.getInstance().getMessage("Editor.Removed") .modify(s -> s.replace("%element%", key)) - .sendAndPlay(player); + .handle(player); }, function); } @@ -384,15 +405,31 @@ public boolean objectIsEditable() { @SuppressWarnings("unchecked") public void editObject(Player player, String key) { if (!objectIsEditable) { - BlobLibAssetAPI.getMessage("Object.Not-Editable").sendAndPlay(player); + BlobLibMessageAPI.getInstance().getMessage("Object.Not-Editable").handle(player); return; } T object = objectManager.getObject(key); if (object == null) { - BlobLibAssetAPI.getMessage("Object.Not-Found").sendAndPlay(player); + BlobLibMessageAPI.getInstance().getMessage("Object.Not-Found").handle(player); return; } ObjectBuilder builder = (ObjectBuilder) object.edit(); getObjectBuilderManager().addBuilder(player.getUniqueId(), builder); } + + public void whenObjectManagerFilesLoad(Consumer> consumer) { + this.objectManager.whenFilesLoad(consumer); + } + + public boolean hasObjectBuilderManager() { + return hasObjectBuilderManager; + } + + @NotNull + public ObjectBuilder getOrDefaultBuilder(UUID uuid) { + if (!hasObjectBuilderManager) + throw new IllegalStateException("ObjectBuilderManager is not enabled. " + + "Implement it in constructor."); + return getObjectBuilderManager().getOrDefault(uuid); + } } diff --git a/src/main/java/us/mytheria/bloblib/entities/ObjectDirectorData.java b/src/main/java/us/mytheria/bloblib/entities/ObjectDirectorData.java index 66dfcf21..b92e6bab 100644 --- a/src/main/java/us/mytheria/bloblib/entities/ObjectDirectorData.java +++ b/src/main/java/us/mytheria/bloblib/entities/ObjectDirectorData.java @@ -34,7 +34,7 @@ public static ObjectDirectorData registerAndBuild(BlobFileManager blobFileManage if (plugin.getResource(fileName) != null) { blobFileManager.addFile(objectBuilderFilename, file); if (blobFileManager.updateYAML(file)) - InventoryManager.continueLoading(plugin, file); + InventoryManager.continueLoadingBlobInventories(plugin, file); } return new ObjectDirectorData(objectDirectoryFilename, objectBuilderFilename, objectName); } diff --git a/src/main/java/us/mytheria/bloblib/entities/ObjectManager.java b/src/main/java/us/mytheria/bloblib/entities/ObjectManager.java index 146334b6..787c6978 100644 --- a/src/main/java/us/mytheria/bloblib/entities/ObjectManager.java +++ b/src/main/java/us/mytheria/bloblib/entities/ObjectManager.java @@ -3,13 +3,18 @@ import me.anjoismysign.anjo.entities.Result; import org.bukkit.entity.Player; import org.jetbrains.annotations.Nullable; +import us.mytheria.bloblib.entities.logger.BlobPluginLogger; import us.mytheria.bloblib.managers.Manager; import us.mytheria.bloblib.managers.ManagerDirector; import java.io.File; -import java.util.AbstractMap; import java.util.Collection; +import java.util.Map; +import java.util.Optional; import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ThreadLocalRandom; +import java.util.function.Consumer; import java.util.function.Supplier; /** @@ -19,14 +24,16 @@ */ public abstract class ObjectManager extends Manager { private final File loadFilesDirectory; - private final Supplier> objectsSupplier; - private final Supplier> fileSupplier; + private final Supplier> objectsSupplier; + private final Supplier> fileSupplier; + private CompletableFuture loadFiles; + private final ObjectDirector parent; /** * The objects that are loaded in random access memory. * Should be initialized in loadInConstructor() method. */ - private AbstractMap objects; - private AbstractMap objectFiles; + private Map objects; + private Map objectFiles; /** * Constructor for ObjectManager @@ -35,12 +42,14 @@ public abstract class ObjectManager extends Manager { * @param loadFilesDirectory The directory to load files from */ public ObjectManager(ManagerDirector managerDirector, File loadFilesDirectory, - Supplier> supplier, - Supplier> fileSupplier) { + Supplier> supplier, + Supplier> fileSupplier, + ObjectDirector parent) { super(managerDirector); this.loadFilesDirectory = loadFilesDirectory; this.objectsSupplier = supplier; this.fileSupplier = fileSupplier; + this.parent = parent; reload(); } @@ -53,24 +62,17 @@ public void addObjectFile(String key, File file) { objectFiles.put(key, file); } - /** - * Logic that should run in super() method in constructor. - */ - @Override - public void loadInConstructor() { - - } - /** * Will load all files in the given directory * * @param path The directory to load files from */ - public abstract void loadFiles(File path); + public abstract void loadFiles(File path, CompletableFuture mainFuture); /** * Adds an object to the manager and * tracks the file it was loaded from. + * If file is null, the object will not be tracked. * * @param key The key of the object * @param file The file of the object @@ -80,7 +82,8 @@ public void addObject(String key, T object, File file) { if (objectFiles.containsKey(key)) return; objects.put(key, object); - objectFiles.put(key, file); + if (file != null) + objectFiles.put(key, file); } /** @@ -107,6 +110,8 @@ public void removeObject(String key) { return; objects.remove(key); File file = objectFiles.get(key); + if (file == null) + return; if (!file.delete()) getPlugin().getAnjoLogger().singleError("Failed to delete file " + file.getName()); objectFiles.remove(key); @@ -121,14 +126,14 @@ public void removeObject(String key) { @Override public void reload() { initializeObjects(); - loadFiles(loadFilesDirectory); + updateLoadFiles(getLoadFilesDirectory()); } - @Nullable /** * @param key The key/fileName of the object * @return The object if found, null otherwise */ + @Nullable public T getObject(String key) { return objects.get(key); } @@ -165,6 +170,20 @@ public Set keys() { return objects.keySet(); } + /** + * Will pick a random object from the manager + * + * @return The object wrapped in an Optional. + * If no objects are in memory, Optional.empty() will be returned. + */ + public Optional pickRandom() { + return values() + .stream() + .skip(ThreadLocalRandom.current() + .nextInt(values().size())) + .findAny(); + } + /** * Will retrieve the loadFilesPath of the ObjectManager * @@ -174,7 +193,34 @@ public File getLoadFilesDirectory() { return loadFilesDirectory; } - public BlobEditor makeEditor(Player player, String dataType) { - return BlobEditor.COLLECTION_INJECTION(player.getUniqueId(), dataType, objects.keySet()); + /** + * Will make a BlobEditor for the given player + * + * @param player The player to make the editor for + * @return A BlobEditor for the given player + */ + public BlobEditor makeEditor(Player player) { + return BlobEditor.COLLECTION_INJECTION_BUILDER(player.getUniqueId(), values(), + parent); + } + + public CompletableFuture getLoadFiles() { + return loadFiles; + } + + public void updateLoadFiles(File path) { + this.loadFiles = new CompletableFuture<>(); + loadFiles(path, loadFiles); + } + + public void whenFilesLoad(Consumer> consumer) { + BlobPluginLogger logger = getPlugin().getAnjoLogger(); + loadFiles.whenComplete((objectManager, throwable) -> { + if (throwable != null) { + logger.singleError(throwable.getMessage()); + return; + } + consumer.accept(this); + }); } } \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/entities/Ownable.java b/src/main/java/us/mytheria/bloblib/entities/Ownable.java new file mode 100644 index 00000000..753cd301 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/Ownable.java @@ -0,0 +1,46 @@ +package us.mytheria.bloblib.entities; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +public interface Ownable { + /** + * Retrieves the current owner of the object. + * + * @return The current owner of the object. + */ + UUID getOwner(); + + /** + * Sets a new owner for the object. + * + * @param newOwner The new owner of the object. + */ + void setOwner(UUID newOwner); + + /** + * Will attempt to retrieve the owner as a Bukkit Player. + * + * @return The owner as a Bukkit Player, null if not online. + */ + @Nullable + default Player findOwner() { + return Bukkit.getPlayer(getOwner()); + } + + /** + * Will attempt to retrieve the owner as a Bukkit Player. + * If the owner is not online, an IllegalStateException will be thrown. + * + * @return The owner as a Bukkit Player. + */ + default Player findOwnerOrFail() { + Player owner = findOwner(); + if (owner == null) + throw new IllegalStateException("Owner is not online!"); + return owner; + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/ParticleContainer.java b/src/main/java/us/mytheria/bloblib/entities/ParticleContainer.java new file mode 100644 index 00000000..18085ecc --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/ParticleContainer.java @@ -0,0 +1,24 @@ +package us.mytheria.bloblib.entities; + +import org.bukkit.Particle; + +/** + * Represents a container for particles. + * Provides methods to get and set particles. + */ +public interface ParticleContainer { + + /** + * Retrieves the current particle. + * + * @return The current particle. + */ + Particle getParticle(); + + /** + * Sets the particle within the container. + * + * @param particle The particle to be set. + */ + void setParticle(Particle particle); +} diff --git a/src/main/java/us/mytheria/bloblib/entities/PlayerTask.java b/src/main/java/us/mytheria/bloblib/entities/PlayerTask.java new file mode 100644 index 00000000..b6d1f3c2 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/PlayerTask.java @@ -0,0 +1,123 @@ +package us.mytheria.bloblib.entities; + +import me.anjoismysign.anjo.entities.Uber; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.scheduler.BukkitTask; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Consumer; + +public class PlayerTask { + private final String name; + private final Uber pending; + private final BukkitTask spectatorTask; + private boolean done; + + /** + * Runs a synchronous PlayerTask + * + * @param player The player to run the task on + * @param plugin The plugin to run the task on + * @param consumer The consumer to run. Expect that player is online and not null. + * @param seconds The amount of seconds to run the task for + */ + public static PlayerTask SYNCHRONOUS(Player player, JavaPlugin plugin, int seconds, Consumer consumer) { + return new PlayerTask(player, plugin, consumer, seconds, false); + } + + /** + * Runs an asynchronous PlayerTask + * + * @param player The player to run the task on + * @param plugin The plugin to run the task on + * @param consumer The consumer to run. Expect that player is online and not null. + * @param seconds The amount of seconds to run the task for + */ + public static PlayerTask ASYNCHRONOUS(Player player, JavaPlugin plugin, int seconds, Consumer consumer) { + return new PlayerTask(player, plugin, consumer, seconds, true); + } + + /** + * Runs a PlayerTask + * + * @param player The player to run the task on + * @param plugin The plugin to run the task on + * @param consumer The consumer to run. Expect that player is online and not null. + * @param seconds The amount of seconds to run the task for + * @param async Whether to run the task asynchronously + */ + public PlayerTask(Player player, JavaPlugin plugin, + Consumer consumer, + int seconds, + boolean async) { + this.name = player.getName(); + this.done = false; + this.pending = Uber.drive(seconds); + this.spectatorTask = async ? new BukkitRunnable() { + @Override + public void run() { + int current = pending.thanks(); + if (current > 0) { + current--; + pending.talk(current); + return; + } + Player player = getPlayer(); + cancel(); + if (player == null) return; + consumer.accept(player); + done = true; + } + }.runTaskTimerAsynchronously(plugin, 0L, 20L) + : + new BukkitRunnable() { + @Override + public void run() { + int current = pending.thanks(); + if (current > 0) { + current--; + pending.talk(current); + return; + } + Player player = getPlayer(); + cancel(); + if (player == null) return; + consumer.accept(player); + done = true; + } + }.runTaskTimer(plugin, 0L, 20L); + } + + public String getName() { + return name; + } + + @Nullable + public Player getPlayer() { + return Bukkit.getPlayer(name); + } + + public boolean isDone() { + return done; + } + + public void cancel() { + spectatorTask.cancel(); + done = true; + } + + public int getPendingSeconds() { + return pending.thanks(); + } + + public void setPendingSeconds(int seconds) { + pending.talk(seconds); + } + + public BukkitTask getTask() { + return spectatorTask; + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/PluginUpdater.java b/src/main/java/us/mytheria/bloblib/entities/PluginUpdater.java new file mode 100644 index 00000000..0cf3a646 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/PluginUpdater.java @@ -0,0 +1,38 @@ +package us.mytheria.bloblib.entities; + +import org.bukkit.plugin.java.JavaPlugin; + +public interface PluginUpdater { + /** + * Will reload the updater and re-run their checks + */ + void reload(); + + /** + * Will return true if there is an update available + * + * @return true if there is an update available + */ + boolean hasAvailableUpdate(); + + /** + * Will get last known latest version of the plugin + * + * @return the latest version of the plugin + */ + String getLatestVersion(); + + /** + * Will attempt to download latest version of the plugin + * + * @return true if the download was successful + */ + boolean download(); + + /** + * Will return the plugin that this updater is for + * + * @return the plugin that this updater is for + */ + JavaPlugin getPlugin(); +} diff --git a/src/main/java/us/mytheria/bloblib/entities/SerializableProfile.java b/src/main/java/us/mytheria/bloblib/entities/SerializableProfile.java new file mode 100644 index 00000000..3fd0c536 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/SerializableProfile.java @@ -0,0 +1,54 @@ +package us.mytheria.bloblib.entities; + +import org.jetbrains.annotations.NotNull; + +import java.io.Serializable; +import java.util.Map; + +/** + * Will be used to represent a profile that can be inside + * a SharedSerializable object (an object that can be + * owned by multiple players). + */ +public interface SerializableProfile extends Serializable { + /** + * Will serialize the profile into a Map.` + * + * @return the serialized profile + */ + @NotNull + default Map serialize() { + return Map.of( + "ProfileName", getProfileName(), + "LastKnownName", getLastKnownName(), + "Identification", getIdentification() + ); + } + + + /** + * May be randomly generated from a text file. + * It is used to identify the profile in case + * the infrastructure allows having multiple profiles + * + * @return the profile name + */ + String getProfileName(); + + /** + * Will store the last known name of the player. + * This must be retrieved when the player joins the server. + * + * @return the last known name of the player + */ + String getLastKnownName(); + + /** + * The identification of the player. + * This is used to identify the player in case + * player changes their name. + * + * @return the identification of the player + */ + String getIdentification(); +} diff --git a/src/main/java/us/mytheria/bloblib/entities/SharedSerializable.java b/src/main/java/us/mytheria/bloblib/entities/SharedSerializable.java new file mode 100644 index 00000000..5b5edad1 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/SharedSerializable.java @@ -0,0 +1,90 @@ +package us.mytheria.bloblib.entities; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Represents an object used in shard/multiple-server instances and can be + * owned by multiple players/owners. + */ +public interface SharedSerializable extends BinarySerializable { + /** + * Gets all proprietors in fast-access memory + * + * @return All proprietors in memory + */ + Map getProprietors(); + + /** + * Adds a proprietor to fast-access memory. + * + * @param proprietor The proprietor to add. + */ + default void addProprietor(T proprietor) { + getProprietors().put(proprietor.getIdentification(), proprietor); + } + + /** + * Removes a proprietor from fast-access memory. + * + * @param identification The identification of the proprietor to remove. + * @return The proprietor that was removed. + */ + default T removeProprietor(String identification) { + return getProprietors().remove(identification); + } + + /** + * Removes a proprietor from fast-access memory. + * + * @param proprietor The proprietor to remove. + * @return The proprietor that was removed. + */ + default T removeProprietor(T proprietor) { + return removeProprietor(proprietor.getIdentification()); + } + + /** + * Will unpack the owners from the document. + * If the document does not contain any owners, it will return an empty list. + * + * @return The owners of this object. + */ + @SuppressWarnings("unchecked") + @NotNull + default List> unpackProprietors() { + List> serializedProprietors = blobCrudable().getDocument().get("SharedSerializable#Proprietors", List.class); + return serializedProprietors == null ? new ArrayList<>() : serializedProprietors; + } + + /** + * Will pack the owners of this object to the document. + */ + default void packProprietors() { + blobCrudable().getDocument().put("SharedSerializable#Proprietors", serializeProprietors()); + } + + /** + * Will serialize the proprietors of this object in a way + * that can later be deserialized, even if the object class + * structure changes. + * + * @return The serialized proprietors. + */ + default List> serializeProprietors() { + return getProprietors().values().stream() + .map(SerializableProfile::serialize).toList(); + } + + /** + * Used inside ProxiedSharedSerializableManager to join a player to a cached + * object in a running session. + * + * @param player The player to join. + */ + void join(Player player); +} diff --git a/src/main/java/us/mytheria/bloblib/entities/SimpleEventListener.java b/src/main/java/us/mytheria/bloblib/entities/SimpleEventListener.java index 87386c26..10a0ad8c 100644 --- a/src/main/java/us/mytheria/bloblib/entities/SimpleEventListener.java +++ b/src/main/java/us/mytheria/bloblib/entities/SimpleEventListener.java @@ -3,6 +3,7 @@ import org.bukkit.configuration.ConfigurationSection; import java.util.List; +import java.util.function.Consumer; public record SimpleEventListener(boolean register, T value) { @@ -116,4 +117,32 @@ public void write(ConfigurationSection section, String path) { section.set("Register", register); section.set(path, value); } + + /** + * If the SimpleEventListener is registered, the consumer will be called + * + * @param consumer The consumer to call + * @return true if the SimpleEventListener is registered, false otherwise + */ + public boolean ifRegister(Consumer> consumer) { + if (register) { + consumer.accept(this); + return true; + } + return false; + } + + /** + * If the SimpleEventListener is not registered, the consumer will be called + * + * @param runnable The runnable to run + * @return true if the SimpleEventListener is not registered, false otherwise + */ + public boolean ifNotRegistered(Runnable runnable) { + if (!register) { + runnable.run(); + return true; + } + return false; + } } \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/entities/SuperInventoryButton.java b/src/main/java/us/mytheria/bloblib/entities/SuperInventoryButton.java deleted file mode 100644 index 7af1f577..00000000 --- a/src/main/java/us/mytheria/bloblib/entities/SuperInventoryButton.java +++ /dev/null @@ -1,25 +0,0 @@ -package us.mytheria.bloblib.entities; - -import java.util.Set; - -public class SuperInventoryButton extends InventoryButton { - private final String command; - private final boolean executesCommand; - - public static SuperInventoryButton fromInventoryButton(InventoryButton button, String command, - boolean executesCommand) { - return new SuperInventoryButton(button.getKey(), button.getSlots(), command, - executesCommand); - } - - public SuperInventoryButton(String key, Set slots, String command, - boolean executesCommand) { - super(key, slots); - this.command = command; - this.executesCommand = executesCommand; - } - - public String getCommand() { - return command; - } -} diff --git a/src/main/java/us/mytheria/bloblib/entities/TinyEventListener.java b/src/main/java/us/mytheria/bloblib/entities/TinyEventListener.java new file mode 100644 index 00000000..3e6a09c9 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/TinyEventListener.java @@ -0,0 +1,58 @@ +package us.mytheria.bloblib.entities; + +import org.bukkit.configuration.ConfigurationSection; + +public record TinyEventListener(boolean register) { + + public static TinyEventListener TRUE() { + return new TinyEventListener(true); + } + + public static TinyEventListener FALSE() { + return new TinyEventListener(false); + } + + /** + * Will read a TinyEventListener from a ConfigurationSection + * Must point to a boolean + * + * @param section The ConfigurationSection to read from + * @param name The name of the TinyEventListener + * @return The TinyEventListener + * @throws RuntimeException If the ConfigurationSection does not point to a boolean + */ + public static TinyEventListener READ(ConfigurationSection section, String name) { + if (!section.isBoolean(name)) + throw new RuntimeException("Not a TinyEventListener"); + boolean register = section.getBoolean(name); + return new TinyEventListener(register); + } + + /** + * If the TinyEventListener is registered, the consumer will be called + * + * @param runnable The runnable to run + * @return true if the TinyEventListener is registered, false otherwise + */ + public boolean ifRegister(Runnable runnable) { + if (register) { + runnable.run(); + return true; + } + return false; + } + + /** + * If the TinyEventListener is not registered, the consumer will be called + * + * @param runnable The runnable to run + * @return true if the TinyEventListener is not registered, false otherwise + */ + public boolean ifNotRegistered(Runnable runnable) { + if (!register) { + runnable.run(); + return true; + } + return false; + } +} \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/entities/VariableEditor.java b/src/main/java/us/mytheria/bloblib/entities/VariableEditor.java index 218b57bb..eb3c7b56 100644 --- a/src/main/java/us/mytheria/bloblib/entities/VariableEditor.java +++ b/src/main/java/us/mytheria/bloblib/entities/VariableEditor.java @@ -1,16 +1,143 @@ package us.mytheria.bloblib.entities; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Consumer; +import java.util.function.Function; + /** * @author anjoismysign *

* A VariableEditor can add and remove - * objects of a collection inside a GUI. + * objects from a collection inside a GUI. + * It extends VariableFiller in order to manage + * the inventory. */ public interface VariableEditor extends VariableFiller { /** * Adds a new object to the editor. + * The change will be reflected in the collection immediately. * * @param t The object to add. */ void add(T t); + + /** + * Removes an object from the editor. + * The change will be reflected in the collection immediately. + * + * @param t The object to remove. + */ + void remove(T t); + + /** + * Selects an object from the editor. + * + * @param player The player that selected the object. + * @param consumer The consumer that will be called when the object is selected. + * @param timerMessageKey The key of the message that will be sent to the player when the timer starts. + */ + void selectElement(Player player, Consumer consumer, @Nullable String timerMessageKey); + + /** + * Selects an object from the editor. + * + * @param player The player that selected the object. + * @param consumer The consumer that will be called when the object is selected. + */ + default void selectElement(Player player, Consumer consumer) { + selectElement(player, consumer, (String) null); + } + + /** + * Selects an object from the editor. + * + * @param player The player that selected the object. + * @param consumer The consumer that will be called when the object is selected. + * @param timerMessageKey The key of the message that will be sent to the player when the timer starts. + * @param function The function that will be called to customize the ItemStack. + */ + void selectElement(Player player, Consumer consumer, @Nullable String timerMessageKey, Function function); + + /** + * Selects an object from the editor. + * + * @param player The player that selected the object. + * @param consumer The consumer that will be called when the object is selected. + * @param function The function that will be called to customize the ItemStack. + */ + default void selectElement(Player player, Consumer consumer, Function function) { + selectElement(player, consumer, null, function); + } + + /** + * Open a menu to the player, so they can choose an element to remove. + * 'Editor.Remove' will be sent to the player while the timer is running. + * + * @param player The player that removed the object. + * @param consumer The consumer that will be called when the object is removed. + */ + default void removeElement(Player player, Consumer consumer) { + selectElement(player, input -> { + remove(input); + consumer.accept(input); + }, "Editor.Remove"); + } + + /** + * Open a menu to the player, so they can choose an element to remove. + * 'Editor.Remove' will be sent to the player while the timer is running. + * The function is for customizing the ItemStack that represents each object. + * + * @param player The player that removed the object. + * @param consumer The consumer that will be called when the object is removed. + * @param function The function that will be called to customize the ItemStack. + */ + default void removeElement(Player player, Consumer consumer, + Function function) { + selectElement(player, input -> { + remove(input); + consumer.accept(input); + }, "Editor.Remove", function); + } + + /** + * Will make the editor listen to the player + * for actions such as navigating through pages, + * adding and removing elements. + * If anything of the previous is done, + * the editor will adapt to the player for that + * specific action. + * If player selects an element, nothing will happen. + * + * @param player The player to manage the editor. + * @param removeConsumer what should happen when an element is removed + */ + void manage(Player player, Consumer removeConsumer); + + /** + * Will make the editor listen to the player + * for actions such as navigating through pages, + * adding and removing elements. + * If anything of the previous is done, + * the editor will adapt to the player for that + * specific action. + * If player selects an element, nothing will happen. + * + * @param player The player to manage the editor. + * @param function the function that loads ItemStacks in inventory + * @param removeConsumer what should happen when an element is removed + */ + void manage(Player player, Function function, + Consumer removeConsumer); + + /** + * Will adjust/adapt to the player so player is able to add an element + * to the current editor. + * + * @param player The player that will be able to add an element. + */ + void addElement(Player player); } \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/entities/WalletProfile.java b/src/main/java/us/mytheria/bloblib/entities/WalletProfile.java new file mode 100644 index 00000000..7bcaa2f0 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/WalletProfile.java @@ -0,0 +1,26 @@ +package us.mytheria.bloblib.entities; + +import org.jetbrains.annotations.NotNull; +import us.mytheria.bloblib.entities.currency.WalletHolder; + +import java.util.Map; + +public interface WalletProfile extends SerializableProfile, WalletHolder { + /** + * Will serialize the profile into a Map. + * Might want to serialize the wallet for + * issues (such as MongoDB) + * + * @return the serialized profile + */ + @Override + @NotNull + default Map serialize() { + return Map.of( + "ProfileName", getProfileName(), + "LastKnownName", getLastKnownName(), + "Identification", getIdentification(), + "Wallet", getWallet().serialize() + ); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/currency/BlobEconomy.java b/src/main/java/us/mytheria/bloblib/entities/currency/BlobEconomy.java index 6b6ada8c..7f78365b 100644 --- a/src/main/java/us/mytheria/bloblib/entities/currency/BlobEconomy.java +++ b/src/main/java/us/mytheria/bloblib/entities/currency/BlobEconomy.java @@ -1,82 +1,29 @@ package us.mytheria.bloblib.entities.currency; -import net.milkbowl.vault.economy.Economy; -import net.milkbowl.vault.economy.EconomyResponse; +import net.milkbowl.vault.economy.IdentityEconomy; +import net.milkbowl.vault.economy.MultiEconomy; +import net.milkbowl.vault.economy.wrappers.MultiEconomyWrapper; import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; -import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.ServicePriority; import us.mytheria.bloblib.managers.BlobPlugin; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; +import java.util.Collection; -public class BlobEconomy implements Economy { +public class BlobEconomy implements MultiEconomy { private final WalletOwnerManager manager; private final BlobPlugin plugin; + private final IdentityEconomy defaultEconomy; - protected BlobEconomy(WalletOwnerManager manager) { + protected BlobEconomy(WalletOwnerManager manager, boolean force) { this.manager = manager; this.plugin = manager.getPlugin(); + manager.updateImplementations(); + this.defaultEconomy = manager.getImplementation(manager.getDefaultCurrency().getKey()); Plugin vault = Bukkit.getPluginManager().getPlugin("Vault"); - if (vault != null) - Bukkit.getServicesManager().register(Economy.class, this, vault, ServicePriority.Normal); - } - - private void updateAsynchronously(T walletOwner) { - Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> - manager.sqlCrudManager.update(walletOwner.serializeAllAttributes())); - } - - public boolean hasRecord(UUID uuid) { - if (manager.owners.containsKey(uuid)) - return true; - return manager.sqlCrudManager.exists(uuid.toString()); - } - - public double getBalance(UUID uuid) { - if (manager.owners.containsKey(uuid)) { - T walletOwner = manager.owners.get(uuid); - return walletOwner.getBalance(manager.getDefaultCurrency()); + if (vault != null) { + MultiEconomyWrapper wrapper = new MultiEconomyWrapper(this); + wrapper.registerProviders(force); } - if (!hasRecord(uuid)) - return 0.0; - T walletOwner = manager.read(uuid).join(); - return walletOwner.getBalance(manager.getDefaultCurrency()); - } - - public boolean has(UUID uuid, double amount) { - return getBalance(uuid) >= amount; - } - - public EconomyResponse withdraw(UUID uuid, double amount) { - if (manager.owners.containsKey(uuid)) { - T walletOwner = manager.owners.get(uuid); - walletOwner.withdraw(manager.getDefaultCurrency(), amount); - return new EconomyResponse(amount, walletOwner.getBalance(manager.getDefaultCurrency()), EconomyResponse.ResponseType.SUCCESS, null); - } - if (!hasRecord(uuid)) - return new EconomyResponse(0.0, 0.0, EconomyResponse.ResponseType.FAILURE, "No record found."); - T walletOwner = manager.read(uuid).join(); - walletOwner.withdraw(manager.getDefaultCurrency(), amount); - updateAsynchronously(walletOwner); - return new EconomyResponse(amount, walletOwner.getBalance(manager.getDefaultCurrency()), EconomyResponse.ResponseType.SUCCESS, null); - } - - public EconomyResponse deposit(UUID uuid, double amount) { - if (manager.owners.containsKey(uuid)) { - T walletOwner = manager.owners.get(uuid); - walletOwner.deposit(manager.getDefaultCurrency(), amount); - return new EconomyResponse(amount, walletOwner.getBalance(manager.getDefaultCurrency()), EconomyResponse.ResponseType.SUCCESS, null); - } - if (!hasRecord(uuid)) - return new EconomyResponse(0.0, 0.0, EconomyResponse.ResponseType.FAILURE, "No record found."); - T walletOwner = manager.read(uuid).join(); - walletOwner.deposit(manager.getDefaultCurrency(), amount); - updateAsynchronously(walletOwner); - return new EconomyResponse(amount, walletOwner.getBalance(manager.getDefaultCurrency()), EconomyResponse.ResponseType.SUCCESS, null); } @Override @@ -90,225 +37,37 @@ public String getName() { } @Override - public boolean hasBankSupport() { - return false; - } - - @Override - public int fractionalDigits() { - return manager.getDefaultCurrency().getDecimalFormat().getMaximumFractionDigits(); - } - - @Override - public String format(double amount) { - return manager.getDefaultCurrency().display(amount); - } - - @Override - public String currencyNamePlural() { - return ""; - } - - @Override - public String currencyNameSingular() { - return ""; - } - - @Override - public boolean hasAccount(String playerName) { - Player player = Bukkit.getPlayer(playerName); - if (player == null) - return false; - return hasRecord(player.getUniqueId()); - } - - @Override - public boolean hasAccount(OfflinePlayer player) { - return hasRecord(player.getUniqueId()); - } - - @Override - public boolean hasAccount(String playerName, String worldName) { - return hasAccount(playerName); - } - - @Override - public boolean hasAccount(OfflinePlayer player, String worldName) { - return hasAccount(player); - } - - @Override - public double getBalance(String playerName) { - Player player = Bukkit.getPlayer(playerName); - if (player == null) - return 0; - return getBalance(player.getUniqueId()); - } - - @Override - public double getBalance(OfflinePlayer player) { - return getBalance(player.getUniqueId()); - } - - @Override - public double getBalance(String playerName, String world) { - return getBalance(playerName); - } - - @Override - public double getBalance(OfflinePlayer player, String world) { - return getBalance(player); - } - - @Override - public boolean has(String playerName, double amount) { - Player player = Bukkit.getPlayer(playerName); - if (player == null) - return false; - return has(player.getUniqueId(), amount); - } - - @Override - public boolean has(OfflinePlayer player, double amount) { - return has(player.getUniqueId(), amount); - } - - @Override - public boolean has(String playerName, String worldName, double amount) { - return has(playerName, amount); + public boolean existsImplementation(String name) { + return manager.currencyDirector.getObjectManager().searchObject(name).isValid(); } @Override - public boolean has(OfflinePlayer player, String worldName, double amount) { - return has(player, amount); + public boolean existsImplementation(String name, String worldName) { + return existsImplementation(name); } @Override - public EconomyResponse withdrawPlayer(String playerName, double amount) { - Player player = Bukkit.getPlayer(playerName); - if (player == null) - return new EconomyResponse(0, 0, EconomyResponse.ResponseType.FAILURE, "Player not found"); - return withdraw(player.getUniqueId(), amount); + public IdentityEconomy getImplementation(String name) { + return manager.getImplementation(name); } @Override - public EconomyResponse withdrawPlayer(OfflinePlayer player, double amount) { - return withdraw(player.getUniqueId(), amount); + public IdentityEconomy getDefault() { + return defaultEconomy; } @Override - public EconomyResponse withdrawPlayer(String playerName, String worldName, double amount) { - return withdrawPlayer(playerName, amount); - } - - @Override - public EconomyResponse withdrawPlayer(OfflinePlayer player, String worldName, double amount) { - return withdrawPlayer(player, amount); - } - - @Override - public EconomyResponse depositPlayer(String playerName, double amount) { - Player player = Bukkit.getPlayer(playerName); - if (player == null) - return new EconomyResponse(0, 0, EconomyResponse.ResponseType.FAILURE, "Player not found"); - return deposit(player.getUniqueId(), amount); - } - - @Override - public EconomyResponse depositPlayer(OfflinePlayer player, double amount) { - return deposit(player.getUniqueId(), amount); - } - - @Override - public EconomyResponse depositPlayer(String playerName, String worldName, double amount) { - return depositPlayer(playerName, amount); - } - - @Override - public EconomyResponse depositPlayer(OfflinePlayer player, String worldName, double amount) { - return depositPlayer(player, amount); - } - - @Override - public EconomyResponse createBank(String name, String player) { - return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Not implemented"); - } - - @Override - public EconomyResponse createBank(String name, OfflinePlayer player) { - return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Not implemented"); - } - - @Override - public EconomyResponse deleteBank(String name) { - return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Not implemented"); - } - - @Override - public EconomyResponse bankBalance(String name) { - return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Not implemented"); - } - - @Override - public EconomyResponse bankHas(String name, double amount) { - return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Not implemented"); - } - - @Override - public EconomyResponse bankWithdraw(String name, double amount) { - return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Not implemented"); - } - - @Override - public EconomyResponse bankDeposit(String name, double amount) { - return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Not implemented"); - } - - @Override - public EconomyResponse isBankOwner(String name, String playerName) { - return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Not implemented"); - } - - @Override - public EconomyResponse isBankOwner(String name, OfflinePlayer player) { - return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Not implemented"); - } - - @Override - public EconomyResponse isBankMember(String name, String playerName) { - return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Not implemented"); - } - - @Override - public EconomyResponse isBankMember(String name, OfflinePlayer player) { - return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Not implemented"); - } - - @Override - public List getBanks() { - plugin.getAnjoLogger().error("A plugin is trying to use Vault's getBanks() method, which is not implemented."); - return new ArrayList<>(); - } - - @Override - public boolean createPlayerAccount(String playerName) { - plugin.getAnjoLogger().error("A plugin is trying to use Vault's createPlayerAccount() method, which is not needed."); - return true; - } - - @Override - public boolean createPlayerAccount(OfflinePlayer player) { - plugin.getAnjoLogger().error("A plugin is trying to use Vault's createPlayerAccount() method, which is not needed."); - return true; + public IdentityEconomy getDefault(String world) { + return getDefault(); } @Override - public boolean createPlayerAccount(String playerName, String worldName) { - return createPlayerAccount(playerName); + public Collection getAllImplementations() { + return manager.retrieveImplementations(); } @Override - public boolean createPlayerAccount(OfflinePlayer player, String worldName) { - return createPlayerAccount(player); + public Collection getAllImplementations(String world) { + return getAllImplementations(); } } diff --git a/src/main/java/us/mytheria/bloblib/entities/currency/BlobEconomyCommand.java b/src/main/java/us/mytheria/bloblib/entities/currency/BlobEconomyCommand.java index 6c78cdbb..935952ce 100644 --- a/src/main/java/us/mytheria/bloblib/entities/currency/BlobEconomyCommand.java +++ b/src/main/java/us/mytheria/bloblib/entities/currency/BlobEconomyCommand.java @@ -4,7 +4,7 @@ import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import us.mytheria.bloblib.BlobLibAssetAPI; +import us.mytheria.bloblib.api.BlobLibMessageAPI; import us.mytheria.bloblib.entities.BlobChildCommand; import us.mytheria.bloblib.entities.BlobExecutor; import us.mytheria.bloblib.entities.ExecutorData; @@ -125,6 +125,8 @@ public boolean command(ExecutorData data) { String[] args = data.args(); BlobExecutor executor = data.executor(); CommandSender sender = data.sender(); + if (!executor.hasAdminPermission(sender)) + return true; Result depositResult = executor .isChildCommand(depositArgumentName, args); if (depositResult.isValid()) { @@ -136,8 +138,8 @@ public boolean command(ExecutorData data) { T walletOwner = context.walletOwner(); Player player = context.player(); walletOwner.deposit(currency, amount); - BlobLibAssetAPI.getMessage("Economy.Deposit").modify(s -> s.replace("%display%", currency.display(amount)) - .replace("%currency%", currency.getKey()) + BlobLibMessageAPI.getInstance().getMessage("Economy.Deposit").modify(s -> s.replace("%display%", currency.display(amount)) + .replace("%currency%", currency.getDisplayName()) .replace("%player%", player.getName())).toCommandSender(sender); return true; } @@ -154,14 +156,14 @@ public boolean command(ExecutorData data) { Player player = context.player(); if (!walletOwner.has(currency, amount)) { double missing = amount - walletOwner.getBalance(currency); - BlobLibAssetAPI.getMessage("Economy.Cannot-Bankrupt-Others").modify(s -> s.replace("%display%", currency.display(missing)) - .replace("%currency%", currency.getKey()) + BlobLibMessageAPI.getInstance().getMessage("Economy.Cannot-Bankrupt-Others").modify(s -> s.replace("%display%", currency.display(missing)) + .replace("%currency%", currency.getDisplayName()) .replace("%player%", player.getName())).toCommandSender(sender); return true; } walletOwner.withdraw(currency, amount); - BlobLibAssetAPI.getMessage("Economy.Withdraw").modify(s -> s.replace("%display%", currency.display(amount)) - .replace("%currency%", currency.getKey()) + BlobLibMessageAPI.getInstance().getMessage("Economy.Withdraw").modify(s -> s.replace("%display%", currency.display(amount)) + .replace("%currency%", currency.getDisplayName()) .replace("%player%", player.getName())).toCommandSender(sender); return true; } @@ -175,8 +177,8 @@ public boolean command(ExecutorData data) { double amount = context.amount(); Player player = context.player(); context.walletOwner().setBalance(currency, amount); - BlobLibAssetAPI.getMessage("Economy.Set").modify(s -> s.replace("%display%", currency.display(amount)) - .replace("%currency%", currency.getKey()) + BlobLibMessageAPI.getInstance().getMessage("Economy.Set").modify(s -> s.replace("%display%", currency.display(amount)) + .replace("%currency%", currency.getDisplayName()) .replace("%player%", player.getName())).toCommandSender(sender); return true; } @@ -189,8 +191,8 @@ public boolean command(ExecutorData data) { Currency currency = context.currency(); Player player = context.player(); context.walletOwner().reset(currency); - BlobLibAssetAPI.getMessage("Economy.Reset").modify(s -> s - .replace("%currency%", currency.getKey()) + BlobLibMessageAPI.getInstance().getMessage("Economy.Reset").modify(s -> s + .replace("%currency%", currency.getDisplayName()) .replace("%player%", player.getName())).toCommandSender(sender); return true; } @@ -201,6 +203,8 @@ public List tabCompleter(ExecutorData data) { String[] args = data.args(); BlobExecutor executor = data.executor(); List suggestions = new ArrayList<>(); + if (!executor.hasAdminPermission(data.sender())) + return suggestions; switch (args.length) { case 1 -> { suggestions.add(depositArgumentName); diff --git a/src/main/java/us/mytheria/bloblib/entities/currency/BlobEconomyCommandContext.java b/src/main/java/us/mytheria/bloblib/entities/currency/BlobEconomyCommandContext.java index 615715be..c85b63e0 100644 --- a/src/main/java/us/mytheria/bloblib/entities/currency/BlobEconomyCommandContext.java +++ b/src/main/java/us/mytheria/bloblib/entities/currency/BlobEconomyCommandContext.java @@ -4,7 +4,7 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.Nullable; -import us.mytheria.bloblib.BlobLibAssetAPI; +import us.mytheria.bloblib.api.BlobLibMessageAPI; import java.util.Optional; @@ -20,12 +20,12 @@ public static BlobEconomyCommandContext WITH_AMOUNT(B String inputPlayer = args[1]; Player player = Bukkit.getPlayer(inputPlayer); if (player == null) { - BlobLibAssetAPI.getMessage("Player.Not-Found").toCommandSender(sender); + BlobLibMessageAPI.getInstance().getMessage("Player.Not-Found").toCommandSender(sender); return null; } Optional optional = command.walletOwnerManager.isWalletOwner(player); if (optional.isEmpty()) { - BlobLibAssetAPI.getMessage("Player.Not-Inside-Plugin-Cache").toCommandSender(sender); + BlobLibMessageAPI.getInstance().getMessage("Player.Not-Inside-Plugin-Cache").toCommandSender(sender); return null; } T walletOwner = optional.get(); @@ -35,7 +35,7 @@ public static BlobEconomyCommandContext WITH_AMOUNT(B try { amount = Double.parseDouble(input); } catch (NumberFormatException e) { - BlobLibAssetAPI.getMessage("Economy.Number-Exception").toCommandSender(sender); + BlobLibMessageAPI.getInstance().getMessage("Economy.Number-Exception").toCommandSender(sender); return null; } Currency currency = command.walletOwnerManager.getDefaultCurrency(); @@ -43,7 +43,7 @@ public static BlobEconomyCommandContext WITH_AMOUNT(B String inputCurrency = args[3]; currency = command.currencyManager.getObject(inputCurrency); if (currency == null) { - BlobLibAssetAPI.getMessage("Currency.Not-Found").toCommandSender(sender); + BlobLibMessageAPI.getInstance().getMessage("Currency.Not-Found").toCommandSender(sender); return null; } } @@ -57,12 +57,12 @@ public static BlobEconomyCommandContext WITHOUT_AMOUN String inputPlayer = args[1]; Player player = Bukkit.getPlayer(inputPlayer); if (player == null) { - BlobLibAssetAPI.getMessage("Player.Not-Found").toCommandSender(sender); + BlobLibMessageAPI.getInstance().getMessage("Player.Not-Found").toCommandSender(sender); return null; } Optional optional = command.walletOwnerManager.isWalletOwner(player); if (optional.isEmpty()) { - BlobLibAssetAPI.getMessage("Player.Not-Inside-Plugin-Cache").toCommandSender(sender); + BlobLibMessageAPI.getInstance().getMessage("Player.Not-Inside-Plugin-Cache").toCommandSender(sender); return null; } T walletOwner = optional.get(); @@ -71,7 +71,7 @@ public static BlobEconomyCommandContext WITHOUT_AMOUN String inputCurrency = args[2]; currency = command.currencyManager.getObject(inputCurrency); if (currency == null) { - BlobLibAssetAPI.getMessage("Currency.Not-Found").toCommandSender(sender); + BlobLibMessageAPI.getInstance().getMessage("Currency.Not-Found").toCommandSender(sender); return null; } } diff --git a/src/main/java/us/mytheria/bloblib/entities/currency/Currency.java b/src/main/java/us/mytheria/bloblib/entities/currency/Currency.java index 404bf84a..850848ac 100644 --- a/src/main/java/us/mytheria/bloblib/entities/currency/Currency.java +++ b/src/main/java/us/mytheria/bloblib/entities/currency/Currency.java @@ -1,20 +1,37 @@ package us.mytheria.bloblib.entities.currency; import org.apache.commons.io.FilenameUtils; +import org.bukkit.NamespacedKey; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import us.mytheria.bloblib.entities.BlobObject; +import us.mytheria.bloblib.exception.ConfigurationFieldException; +import us.mytheria.bloblib.itemstack.ItemStackReader; +import us.mytheria.bloblib.managers.BlobPlugin; +import us.mytheria.bloblib.managers.ManagerDirector; import us.mytheria.bloblib.utilities.Formatter; import us.mytheria.bloblib.utilities.TextColor; import java.io.File; +import java.math.BigDecimal; +import java.math.RoundingMode; import java.text.DecimalFormat; +import java.util.*; public class Currency implements BlobObject { - private final String display; - private final String key; + private final String display, key, displayName; private final double initialBalance; private final boolean isPersistent; private final DecimalFormat decimalFormat; + private final boolean isTangible; + private final @Nullable Map tangibleShapes; + private final BlobPlugin plugin; /** * Creates a new Currency. @@ -26,12 +43,21 @@ public class Currency implements BlobObject { * @param key the key */ public Currency(String display, double initialBalance, boolean isPersistent, - String pattern, String key) { + String pattern, String key, boolean isTangible, + @Nullable Map tangibleShapes, + BlobPlugin plugin, String displayName) { + this.plugin = plugin; this.display = display; this.key = key; + this.displayName = displayName; this.initialBalance = initialBalance; this.isPersistent = isPersistent; this.decimalFormat = new DecimalFormat(pattern); + this.isTangible = isTangible; + if (tangibleShapes != null && tangibleShapes.isEmpty()) + this.tangibleShapes = null; + else + this.tangibleShapes = tangibleShapes; } /** @@ -43,6 +69,106 @@ public String getKey() { return key; } + /** + * If the currency is tangible, which means that it + * can be dropped on the ground, be picked up, etc. + * + * @return whether the currency is tangible + */ + public boolean isTangible() { + return isTangible; + } + + /** + * If the currency is tangible, it will return the ItemStacks + * which is the way the currency is displayed in the game. + * ItemStacks won't be unique, which means players can stack + * them in inventories. + * + * @param amount the reminder of this currency (not the item) + * to split into ItemStacks + * @return the TangibleShapeOperation. Check if it's valid + * and if it has a reminder. Reminder means that the currency + * cannot be split into ItemStacks with the current denominations + * completely! + */ + public TangibleShapeOperation getTangibleShape(double amount) { + return getTangibleShape(amount, false); + } + + /** + * If the currency is tangible, it will return the ItemStacks + * which is the way the currency is displayed in the game. + * + * @param amount the reminder of this currency (not the item) + * to split into ItemStacks + * @param unique whether the ItemStacks should be unique + * @return the TangibleShapeOperation. Check if it's valid + * and if it has a reminder. Reminder means that the currency + * cannot be split into ItemStacks with the current denominations + * completely! + */ + @NotNull + public TangibleShapeOperation getTangibleShape(double amount, boolean unique) { + if (!isTangible) + return TangibleShapeOperation.INVALID(); + if (amount <= 0) + return TangibleShapeOperation.INVALID(); + if (tangibleShapes == null) + return TangibleShapeOperation.INVALID(); + List list = new ArrayList<>(); + SplitResult result = split(BigDecimal.valueOf(amount), tangibleShapes.keySet().stream() + .map(BigDecimal::new).toArray(BigDecimal[]::new)); + Map split = result.split(); + if (split.isEmpty()) + return TangibleShapeOperation.INVALID(); + split.forEach((x, y) -> { + ItemStack clone = new ItemStack(tangibleShapes.get(x.toString())); + clone.setAmount(y); + if (unique) { + ItemMeta itemMeta = clone.getItemMeta(); + PersistentDataContainer pdc = itemMeta.getPersistentDataContainer(); + pdc.set(new NamespacedKey(plugin, "unique"), + PersistentDataType.STRING, UUID.randomUUID().toString()); + clone.setItemMeta(itemMeta); + } + list.add(clone); + }); + return new TangibleShapeOperation(list, result.reminder()); + } + + private static SplitResult split(BigDecimal amount, BigDecimal[] denominations) { + Map result = new HashMap<>(); + Arrays.sort(denominations, Comparator.reverseOrder()); + + for (BigDecimal denomination : denominations) { + int count = amount.divide(denomination, RoundingMode.FLOOR).intValue(); + if (count > 0) { + result.put(denomination, count); + amount = amount.subtract(denomination.multiply(BigDecimal.valueOf(count))); + } + } + + return new SplitResult(result, amount); + } + + private record SplitResult(Map split, BigDecimal reminder) { + } + + public record TangibleShapeOperation(List shape, BigDecimal reminder) { + private static TangibleShapeOperation INVALID() { + return new TangibleShapeOperation(Collections.emptyList(), BigDecimal.ZERO); + } + + public boolean hasReminder() { + return reminder.compareTo(BigDecimal.ZERO) > 0; + } + + public boolean isValid() { + return !shape.isEmpty(); + } + } + /** * How the currency is displayed in the game. * @@ -53,10 +179,19 @@ private String getDisplay() { } /** - * Returns a String that replaces inside customName '%amount%' - * with the given amount. + * Returns how its name is displayed in the game. + * + * @return the display name + */ + public String getDisplayName() { + return displayName; + } + + /** + * Returns a String that replaces inside customName '%reminder%' + * with the given reminder. * - * @param amount the amount to display + * @param amount the reminder to display * @return the formatted String */ public String display(double amount) { @@ -102,6 +237,7 @@ public File saveToFile(File directory) { yamlConfiguration.set("InitialBalance", initialBalance); yamlConfiguration.set("Persistent", isPersistent); yamlConfiguration.set("DecimalFormat", decimalFormat.toPattern()); + yamlConfiguration.set("Is-Tangible", isTangible); try { yamlConfiguration.save(file); } catch (Exception exception) { @@ -110,14 +246,44 @@ public File saveToFile(File directory) { return file; } - public static Currency fromFile(File file) { + public static Currency fromFile(File file, ManagerDirector director) { String fileName = file.getName(); YamlConfiguration yamlConfiguration = YamlConfiguration.loadConfiguration(file); String display = TextColor.PARSE(yamlConfiguration.getString("Display")); - double initialBalance = yamlConfiguration.getDouble("InitialBalance"); + double initialBalance = yamlConfiguration.getDouble("Initial-Balance"); boolean isPersistent = yamlConfiguration.getBoolean("Persistent"); - String pattern = yamlConfiguration.getString("DecimalFormat"); + String pattern = yamlConfiguration.getString("Decimal-Format"); String key = FilenameUtils.removeExtension(fileName); - return new Currency(display, initialBalance, isPersistent, pattern, key); + String displayName = key; + if (yamlConfiguration.isString("Display-Name")) + displayName = TextColor.PARSE(yamlConfiguration.getString("Display-Name")); + boolean isTangible = yamlConfiguration.getBoolean("Is-Tangible", false); + final Map tangibleShape = new HashMap<>(); + if (isTangible) { + ConfigurationSection shapesSection = yamlConfiguration.getConfigurationSection("Tangible-Shapes"); + if (shapesSection == null) + throw new ConfigurationFieldException("Tangible-Shape section is missing"); + shapesSection.getKeys(false).forEach(reference -> { + String a = "Tangible-Shapes." + reference; + if (!shapesSection.isConfigurationSection(reference)) + throw new ConfigurationFieldException("'" + a + "' is not valid"); + ConfigurationSection shape = shapesSection.getConfigurationSection(reference); + if (!shape.isConfigurationSection("ItemStack")) + throw new ConfigurationFieldException("'" + a + ".ItemStack' is missing or not valid"); + ConfigurationSection itemSection = shape.getConfigurationSection("ItemStack"); + if (!shape.isDouble("Denomination")) + throw new ConfigurationFieldException("'" + a + ".Denomination' is missing or not valid (DECIMAL NUMBER)"); + BigDecimal denomination = BigDecimal.valueOf(shape.getDouble("Denomination")); + ItemStack builder = ItemStackReader.READ_OR_FAIL_FAST(itemSection).build(); + ItemMeta itemMeta = builder.getItemMeta(); + PersistentDataContainer pdc = itemMeta.getPersistentDataContainer(); + pdc.set(director.getNamespacedKey("tangibleCurrencyKey"), PersistentDataType.STRING, key); + pdc.set(director.getNamespacedKey("tangibleCurrencyDenomination"), PersistentDataType.STRING, denomination.toString()); + builder.setItemMeta(itemMeta); + tangibleShape.put(denomination.toString(), builder); + }); + } + return new Currency(display, initialBalance, isPersistent, pattern, key, + isTangible, tangibleShape, director.getPlugin(), displayName); } } \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/entities/currency/CurrencyBuilder.java b/src/main/java/us/mytheria/bloblib/entities/currency/CurrencyBuilder.java index 41adc635..a4a6f9e3 100644 --- a/src/main/java/us/mytheria/bloblib/entities/currency/CurrencyBuilder.java +++ b/src/main/java/us/mytheria/bloblib/entities/currency/CurrencyBuilder.java @@ -1,7 +1,8 @@ package us.mytheria.bloblib.entities.currency; import org.bukkit.entity.Player; -import us.mytheria.bloblib.BlobLibAssetAPI; +import us.mytheria.bloblib.api.BlobLibInventoryAPI; +import us.mytheria.bloblib.api.BlobLibSoundAPI; import us.mytheria.bloblib.entities.ObjectDirector; import us.mytheria.bloblib.entities.inventory.BlobInventory; import us.mytheria.bloblib.entities.inventory.ObjectBuilder; @@ -21,7 +22,7 @@ that is called by interacting (lets say by clicking through InventoryClickEvent) public static CurrencyBuilder build(UUID builderId, ObjectDirector objectDirector) { return new CurrencyBuilder( - BlobLibAssetAPI.buildInventory("CurrencyBuilder"), builderId, + BlobLibInventoryAPI.getInstance().buildInventory("CurrencyBuilder"), builderId, objectDirector); } @@ -36,7 +37,7 @@ private CurrencyBuilder(BlobInventory blobInventory, UUID builderId, "DecimalFormat", 300, this); ObjectBuilderButton initialBalance = ObjectBuilderButtonBuilder.POSITIVE_DOUBLE( "InitialBalance", 300, this); - addObjectBuilderButton(displayButton) + addObjectBuilderButton(keyButton) .addObjectBuilderButton(displayButton) .addObjectBuilderButton(pattern) .addObjectBuilderButton(initialBalance) @@ -45,10 +46,9 @@ private CurrencyBuilder(BlobInventory blobInventory, UUID builderId, if (build == null) return null; Player player = getPlayer(); - BlobSound sound = BlobLibAssetAPI.getSound("Builder.Build-Complete"); + BlobSound sound = BlobLibSoundAPI.getInstance().getSound("Builder.Build-Complete"); sound.play(player); player.closeInventory(); - build.saveToFile(objectDirector.getObjectManager().getLoadFilesDirectory()); objectDirector.getObjectManager().addObject(build.getKey(), build); objectDirector.getBuilderManager().removeBuilder(player); return build; @@ -64,14 +64,18 @@ public Currency construct() { ObjectBuilderButton pattern = (ObjectBuilderButton) getObjectBuilderButton("DecimalFormat"); ObjectBuilderButton initialBalance = (ObjectBuilderButton) getObjectBuilderButton("InitialBalance"); - if (keyButton.get().isEmpty() || displayButton.get().isEmpty() || pattern.get().isEmpty() || initialBalance.get().isEmpty()) + if (!keyButton.isValuePresentAndNotNull() || !displayButton.isValuePresentAndNotNull() + || !pattern.isValuePresentAndNotNull() || !initialBalance.isValuePresentAndNotNull()) return null; String key = keyButton.get().get(); String display = displayButton.get().get(); String decimalFormatPattern = pattern.get().get(); double initialBalanceValue = initialBalance.get().get(); - - return new Currency(display, initialBalanceValue, true, decimalFormatPattern, key); + + return new Currency(display, initialBalanceValue, + true, decimalFormatPattern, key, + false, null, getObjectDirector().getPlugin(), + key); } } \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/entities/currency/CurrencyEconomy.java b/src/main/java/us/mytheria/bloblib/entities/currency/CurrencyEconomy.java new file mode 100644 index 00000000..905cd20f --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/currency/CurrencyEconomy.java @@ -0,0 +1,403 @@ +package us.mytheria.bloblib.entities.currency; + +import me.anjoismysign.anjo.entities.Uber; +import net.milkbowl.vault.economy.EconomyResponse; +import net.milkbowl.vault.economy.IdentityEconomy; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; + +import javax.naming.OperationNotSupportedException; +import java.util.*; +import java.util.function.Consumer; + +public class CurrencyEconomy implements IdentityEconomy { + private final Currency currency; + private final WalletOwnerManager walletOwnerManager; + + protected CurrencyEconomy(Currency currency, WalletOwnerManager walletOwnerManager) { + this.currency = currency; + this.walletOwnerManager = walletOwnerManager; + } + + private Optional isOnline(UUID uuid) { + return walletOwnerManager.isWalletOwner(uuid); + } + + private boolean ifIsOnline(UUID uuid, Consumer consumer) { + Optional walletOwner = isOnline(uuid); + if (walletOwner.isPresent()) { + consumer.accept(walletOwner.get()); + return true; + } else + return false; + } + + @Override + public boolean supportsAllRecordsOperation() { + return false; + } + + @Override + public boolean supportsAllOnlineOperation() { + return true; + } + + @Override + public boolean supportsOfflineOperations() { + return false; + } + + @Override + public boolean createAccount(UUID uuid, String s) { + return true; + } + + @Override + public boolean createAccount(UUID uuid, String s, String s1) { + return true; + } + + @Override + public Map getAllRecords() { + try { + throw new OperationNotSupportedException("getAllRecords() is not supported by CurrencyEconomy"); + } catch (OperationNotSupportedException e) { + throw new RuntimeException(e); + } + } + + @Override + public Collection getAllOnline() { + return walletOwnerManager.walletOwners.keySet(); + } + + @Override + public String getAccountName(UUID uuid) { + Uber name = Uber.fly(); + boolean isOnline = ifIsOnline(uuid, walletOwner -> + name.talk(walletOwner.getPlayerName())); + if (!isOnline) + throw new IllegalArgumentException("Not online " + uuid); + return name.thanks(); + } + + @Override + public boolean hasAccount(UUID uuid) { + return walletOwnerManager.isWalletOwner(uuid).isPresent(); + } + + @Override + public boolean hasAccount(UUID uuid, String s) { + return hasAccount(uuid); + } + + public boolean renameAccount(UUID uuid, String name) { + return isOnline(uuid).isPresent(); + } + + @Override + public double getBalance(UUID uuid) { + Uber balance = Uber.fly(); + boolean hasRecord = ifIsOnline(uuid, walletOwner -> + balance.talk(walletOwner.getBalance(currency.getKey()))); + if (!hasRecord) + return 0; + return balance.thanks(); + } + + @Override + public double getBalance(UUID uuid, String s) { + return getBalance(uuid); + } + + @Override + public boolean has(UUID uuid, double amount) { + return getBalance(uuid) >= amount; + } + + @Override + public boolean has(UUID uuid, String s, double amount) { + return getBalance(uuid) >= amount; + } + + @Override + public EconomyResponse withdraw(UUID uuid, double amount) { + Uber has = Uber.fly(); + ifIsOnline(uuid, walletOwner -> { + if (!has(uuid, amount)) + has.talk(false); + walletOwner.withdraw(currency.getKey(), amount); + has.talk(true); + }); + if (has.thanks()) + return new EconomyResponse(amount, getBalance(uuid), EconomyResponse.ResponseType.SUCCESS, null); + else + return new EconomyResponse(amount, getBalance(uuid), EconomyResponse.ResponseType.FAILURE, null); + } + + @Override + public EconomyResponse withdraw(UUID uuid, String s, double amount) { + return withdraw(uuid, amount); + } + + @Override + public EconomyResponse deposit(UUID uuid, double amount) { + Uber has = new Uber<>(false); + ifIsOnline(uuid, walletOwner -> { + walletOwner.deposit(currency.getKey(), amount); + has.talk(true); + }); + if (has.thanks()) + return new EconomyResponse(amount, getBalance(uuid), EconomyResponse.ResponseType.SUCCESS, null); + else + return new EconomyResponse(amount, getBalance(uuid), EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Offline operations not implemented"); + } + + @Override + public EconomyResponse deposit(UUID uuid, String s, double amount) { + return deposit(uuid, amount); + } + + @Override + public EconomyResponse createBank(String s, UUID uuid) { + return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Not implemented"); + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public String getName() { + return currency.getKey(); + } + + @Override + public boolean hasBankSupport() { + return false; + } + + @Override + public int fractionalDigits() { + return currency.getDecimalFormat().getMinimumFractionDigits(); + } + + @Override + public String format(double amount) { + return currency.display(amount); + } + + @Override + public String currencyNamePlural() { + return ""; + } + + @Override + public String currencyNameSingular() { + return ""; + } + + @Override + public boolean hasAccount(String s) { + return false; + } + + @Override + public boolean hasAccount(OfflinePlayer offlinePlayer) { + return false; + } + + @Override + public boolean hasAccount(String s, String s1) { + return false; + } + + @Override + public boolean hasAccount(OfflinePlayer offlinePlayer, String s) { + return false; + } + + @Override + public double getBalance(String playerName) { + Player player = Bukkit.getPlayer(playerName); + if (player == null) + return 0; + return getBalance(player.getUniqueId()); + } + + @Override + public double getBalance(OfflinePlayer offlinePlayer) { + return getBalance(offlinePlayer.getUniqueId()); + } + + @Override + public double getBalance(String playerName, String s1) { + return getBalance(playerName); + } + + @Override + public double getBalance(OfflinePlayer offlinePlayer, String s) { + return getBalance(offlinePlayer); + } + + @Override + public boolean has(String playerName, double amount) { + Player player = Bukkit.getPlayer(playerName); + if (player == null) + return false; + return has(player.getUniqueId(), amount); + } + + @Override + public boolean has(OfflinePlayer offlinePlayer, double amount) { + return has(offlinePlayer.getUniqueId(), amount); + } + + @Override + public boolean has(String playerName, String s1, double amount) { + return has(playerName, amount); + } + + @Override + public boolean has(OfflinePlayer offlinePlayer, String s, double amount) { + return has(offlinePlayer, amount); + } + + @Override + public EconomyResponse withdrawPlayer(String playerName, double amount) { + Player player = Bukkit.getPlayer(playerName); + if (player == null) + return new EconomyResponse(amount, 0, EconomyResponse.ResponseType.FAILURE, "Player not online"); + return withdraw(player.getUniqueId(), amount); + } + + @Override + public EconomyResponse withdrawPlayer(OfflinePlayer offlinePlayer, double amount) { + return withdrawPlayer(offlinePlayer.getName(), amount); + } + + @Override + public EconomyResponse withdrawPlayer(String playerName, String s1, double amount) { + return withdrawPlayer(playerName, amount); + } + + @Override + public EconomyResponse withdrawPlayer(OfflinePlayer offlinePlayer, String s, double amount) { + return withdrawPlayer(offlinePlayer.getName(), amount); + } + + @Override + public EconomyResponse depositPlayer(String playerName, double amount) { + Player player = Bukkit.getPlayer(playerName); + if (player == null) + return new EconomyResponse(amount, 0, EconomyResponse.ResponseType.FAILURE, "Player not online"); + return deposit(player.getUniqueId(), amount); + } + + @Override + public EconomyResponse depositPlayer(OfflinePlayer offlinePlayer, double amount) { + return depositPlayer(offlinePlayer.getName(), amount); + } + + @Override + public EconomyResponse depositPlayer(String playerName, String s1, double amount) { + return depositPlayer(playerName, amount); + } + + @Override + public EconomyResponse depositPlayer(OfflinePlayer offlinePlayer, String s, double amount) { + return depositPlayer(offlinePlayer.getName(), amount); + } + + @Override + public EconomyResponse createBank(String s, String s1) { + return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Not implemented"); + } + + @Override + public EconomyResponse createBank(String s, OfflinePlayer offlinePlayer) { + return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Not implemented"); + } + + @Override + public EconomyResponse deleteBank(String s) { + return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Not implemented"); + } + + @Override + public EconomyResponse bankBalance(String s) { + return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Not implemented"); + } + + @Override + public EconomyResponse bankHas(String s, double v) { + return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Not implemented"); + } + + @Override + public EconomyResponse bankWithdraw(String s, double v) { + return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Not implemented"); + } + + @Override + public EconomyResponse bankDeposit(String s, double v) { + return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Not implemented"); + } + + @Override + public EconomyResponse isBankOwner(String s, String s1) { + return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Not implemented"); + } + + @Override + public EconomyResponse isBankOwner(String s, OfflinePlayer offlinePlayer) { + return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Not implemented"); + } + + @Override + public EconomyResponse isBankMember(String s, String s1) { + return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Not implemented"); + } + + @Override + public EconomyResponse isBankMember(String s, OfflinePlayer offlinePlayer) { + return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Not implemented"); + } + + @Override + public EconomyResponse isBankOwner(String s, UUID uuid) { + return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Not implemented"); + } + + @Override + public EconomyResponse isBankMember(String s, UUID uuid) { + return new EconomyResponse(0, 0, EconomyResponse.ResponseType.NOT_IMPLEMENTED, "Not implemented"); + } + + @Override + public List getBanks() { + return new ArrayList<>(); + } + + @Override + public boolean createPlayerAccount(String playerName) { + return true; + } + + @Override + public boolean createPlayerAccount(OfflinePlayer offlinePlayer) { + return true; + } + + @Override + public boolean createPlayerAccount(String playerName, String s1) { + return true; + } + + @Override + public boolean createPlayerAccount(OfflinePlayer offlinePlayer, String s) { + return true; + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/currency/EconomyFactory.java b/src/main/java/us/mytheria/bloblib/entities/currency/EconomyFactory.java index 7f38176f..450f559e 100644 --- a/src/main/java/us/mytheria/bloblib/entities/currency/EconomyFactory.java +++ b/src/main/java/us/mytheria/bloblib/entities/currency/EconomyFactory.java @@ -6,7 +6,6 @@ import us.mytheria.bloblib.entities.ObjectDirectorData; import us.mytheria.bloblib.managers.ManagerDirector; -import java.util.UUID; import java.util.function.Function; public class EconomyFactory { @@ -17,9 +16,10 @@ public class EconomyFactory { * * @param managerDirector The ManagerDirector */ - public static ObjectDirector CURRENCY_DIRECTOR(ManagerDirector managerDirector) { - ObjectDirector director = new ObjectDirector<>(managerDirector, ObjectDirectorData.simple(managerDirector.getFileManager(), - "Currency"), Currency::fromFile); + public static ObjectDirector CURRENCY_DIRECTOR(ManagerDirector managerDirector, + String objectName) { + ObjectDirector director = new ObjectDirector<>(managerDirector, ObjectDirectorData.simple(managerDirector.getRealFileManager(), + objectName), file -> Currency.fromFile(file, managerDirector)); director.getBuilderManager().setBuilderBiFunction( CurrencyBuilder::build); return director; @@ -42,7 +42,7 @@ public static ObjectDirector CURRENCY_DIRECTOR(ManagerDirector manager * @return A new WalletOwnerManager. */ public static WalletOwnerManager SIMPLE_WALLET_OWNER_MANAGER(ManagerDirector managerDirector, - Function newBorn, + Function newBorn, Function walletOwner, String crudableName, boolean logActivity) { return new WalletOwnerManager(managerDirector, newBorn, walletOwner, crudableName, logActivity, null, null); @@ -71,7 +71,7 @@ public static WalletOwnerManager SIMPLE_WALLET_OWNER_ * @return A new WalletOwnerManager. */ public static WalletOwnerManager WALLET_OWNER_MANAGER(ManagerDirector managerDirector, - Function newBorn, + Function newBorn, Function walletOwner, String crudableName, boolean logActivity, Function joinEvent, diff --git a/src/main/java/us/mytheria/bloblib/entities/currency/EconomyPHExpansion.java b/src/main/java/us/mytheria/bloblib/entities/currency/EconomyPHExpansion.java index 8caf90ed..9f331d1e 100644 --- a/src/main/java/us/mytheria/bloblib/entities/currency/EconomyPHExpansion.java +++ b/src/main/java/us/mytheria/bloblib/entities/currency/EconomyPHExpansion.java @@ -23,11 +23,11 @@ public boolean canRegister() { } public @NotNull String getIdentifier() { - return ownerManager.getPlugin().getName().toLowerCase(); + return ownerManager.getPlugin().getName().toLowerCase() + "economy"; } public @NotNull String getVersion() { - return "1.0.0"; + return "1.0.1"; } public String onRequest(OfflinePlayer player, String identifier) { @@ -35,7 +35,7 @@ public String onRequest(OfflinePlayer player, String identifier) { if (split.length == 2) { Set customCrypto = ownerManager.currencyDirector.getObjectManager().keys(); if (!customCrypto.contains(split[0])) - return "Invalid crypto: " + split[0]; + return "Invalid currency: " + split[0]; Optional optional = ownerManager.isWalletOwner(player.getUniqueId()); if (optional.isEmpty()) return "Invalid player: " + player.getName(); diff --git a/src/main/java/us/mytheria/bloblib/entities/currency/TangibleCurrencyListener.java b/src/main/java/us/mytheria/bloblib/entities/currency/TangibleCurrencyListener.java new file mode 100644 index 00000000..0d9207ad --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/currency/TangibleCurrencyListener.java @@ -0,0 +1,36 @@ +package us.mytheria.bloblib.entities.currency; + +import org.bukkit.Material; +import org.bukkit.event.Listener; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.jetbrains.annotations.Nullable; +import us.mytheria.bloblib.managers.ManagerDirector; + +//Under development +public class TangibleCurrencyListener implements Listener { + private final ManagerDirector director; + + public TangibleCurrencyListener(ManagerDirector director) { + this.director = director; + } + + @Nullable + private String isCurrency(ItemStack itemStack) { + if (itemStack == null) + return null; + if (itemStack.getType() == Material.AIR) + return null; + ItemMeta itemMeta = itemStack.getItemMeta(); + if (itemMeta == null) + return null; + PersistentDataContainer pdc = itemMeta.getPersistentDataContainer(); + if (!pdc.has(director.getNamespacedKey("tangibleCurrencyKey"), + PersistentDataType.STRING)) + return null; + return pdc.get(director.getNamespacedKey("tangibleCurrencyKey"), + PersistentDataType.STRING); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/currency/Wallet.java b/src/main/java/us/mytheria/bloblib/entities/currency/Wallet.java index b3d00431..6d63c157 100644 --- a/src/main/java/us/mytheria/bloblib/entities/currency/Wallet.java +++ b/src/main/java/us/mytheria/bloblib/entities/currency/Wallet.java @@ -2,13 +2,20 @@ import org.bson.Document; +import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -public class Wallet extends HashMap { - protected void serializeInDocument(Document document) { +public class Wallet extends HashMap implements Serializable { + public static Wallet deserialize(Map map) { + Wallet wallet = new Wallet(); + map.forEach((k, v) -> wallet.put(k, (Double) v)); + return wallet; + } + + public void serializeInDocument(Document document) { Map map = new HashMap<>(); forEach((k, v) -> map.put(k, v.toString())); List list = new ArrayList<>(); @@ -16,20 +23,26 @@ protected void serializeInDocument(Document document) { document.put("Wallet", list); } - protected void add(String key, double amount) { + public Map serialize() { + return new HashMap<>(this); + } + + public void add(String key, double amount) { compute(key, (k, v) -> v == null ? amount : v + amount); } - protected void subtract(String key, double amount) { + public void subtract(String key, double amount) { if (containsKey(key)) put(key, get(key) - amount); } - protected boolean has(String key, double amount) { - return containsKey(key) && get(key) >= amount; + public boolean has(String key, double amount) { + Double result = get(key); + return result != null && result.compareTo(amount) >= 0; } - protected double balance(String key) { - return containsKey(key) ? get(key) : 0; + public double balance(String key) { + Double result = get(key); + return result == null ? 0 : result; } } diff --git a/src/main/java/us/mytheria/bloblib/entities/currency/WalletHolder.java b/src/main/java/us/mytheria/bloblib/entities/currency/WalletHolder.java new file mode 100644 index 00000000..fd540fd5 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/currency/WalletHolder.java @@ -0,0 +1,132 @@ +package us.mytheria.bloblib.entities.currency; + +public interface WalletHolder { + /** + * Should not interact with the wallet directly, use the methods below. + * + * @return the wallet + */ + Wallet getWallet(); + + /** + * Deposits the amount into the wallet. + *

+ * + * @param currency the currency + * @param amount the amount + */ + default void deposit(Currency currency, double amount) { + deposit(currency.getKey(), amount); + } + + /** + * Deposits the amount into the wallet. + *

+ * + * @param key the key + * @param amount the amount + */ + default void deposit(String key, double amount) { + getWallet().add(key, amount); + } + + /** + * Withdraws the amount from the wallet. + *

+ * + * @param currency the currency + * @param amount the amount + */ + default void withdraw(Currency currency, double amount) { + withdraw(currency.getKey(), amount); + } + + /** + * Withdraws the amount from the wallet. + *

+ * + * @param key the key + * @param amount the amount + */ + default void withdraw(String key, double amount) { + getWallet().subtract(key, amount); + } + + /** + * Checks if the wallet has the amount. + *

+ * + * @param currency the currency + * @param amount the amount + * @return true if the wallet has the amount + */ + default boolean has(Currency currency, double amount) { + return has(currency.getKey(), amount); + } + + /** + * Checks if the wallet has the amount. + *

+ * + * @param key the key + * @param amount the amount + * @return true if the wallet has the amount + */ + default boolean has(String key, double amount) { + return getWallet().has(key, amount); + } + + /** + * Gets the balance of the wallet. + *

+ * + * @param currency the currency + * @return the balance + */ + default double getBalance(Currency currency) { + return getBalance(currency.getKey()); + } + + /** + * Gets the balance of the wallet. + *

+ * + * @param key the key + * @return the balance + */ + default double getBalance(String key) { + return getWallet().balance(key); + } + + /** + * Sets the balance of the wallet. + *

+ * + * @param currency the currency + * @param amount the amount + */ + default void setBalance(Currency currency, double amount) { + setBalance(currency.getKey(), amount); + } + + /** + * Sets the balance of the wallet. + *

+ * + * @param key the key + * @param amount the amount + */ + default void setBalance(String key, double amount) { + getWallet().put(key, amount); + } + + /** + * Resets the wallet to the initial balance. + *

+ * + * @param currency the currency + */ + default void reset(Currency currency) { + setBalance(currency.getKey(), currency.getInitialBalance()); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/currency/WalletOwner.java b/src/main/java/us/mytheria/bloblib/entities/currency/WalletOwner.java index 521565e5..bfd4e573 100644 --- a/src/main/java/us/mytheria/bloblib/entities/currency/WalletOwner.java +++ b/src/main/java/us/mytheria/bloblib/entities/currency/WalletOwner.java @@ -2,12 +2,16 @@ import org.bson.Document; import us.mytheria.bloblib.entities.BlobCrudable; +import us.mytheria.bloblib.entities.BlobSerializable; import java.util.ArrayList; import java.util.List; -import java.util.UUID; -public interface WalletOwner { +public interface WalletOwner extends BlobSerializable, WalletHolder { + String getPlayerName(); + + String getPlayerUniqueId(); + /** * Each WalletOwner should hold a BlobCrudable which * will be used to serialize and deserialize not only @@ -19,6 +23,7 @@ public interface WalletOwner { * * @return the BlobCrudable */ + @Override BlobCrudable blobCrudable(); /** @@ -33,6 +38,7 @@ public interface WalletOwner { * * @return the updated BlobCrudable */ + @Override BlobCrudable serializeAllAttributes(); /** @@ -64,163 +70,12 @@ default Wallet deserializeWallet() { return wallet; } - /** - * Should not interact with the wallet directly, use the methods below. - * - * @return the wallet - */ - Wallet getWallet(); - - /** - * Deposits the amount into the wallet. - *

- * Should not interact with the wallet directly, use the methods below. - *

- * - * @param currency the currency - * @param amount the amount - */ - default void deposit(Currency currency, double amount) { - deposit(currency.getKey(), amount); - } - - /** - * Deposits the amount into the wallet. - *

- * Should not interact with the wallet directly, use the methods below. - *

- * - * @param key the key - * @param amount the amount - */ - default void deposit(String key, double amount) { - getWallet().add(key, amount); - } - - /** - * Withdraws the amount from the wallet. - *

- * Should not interact with the wallet directly, use the methods below. - *

- * - * @param currency the currency - * @param amount the amount - */ - default void withdraw(Currency currency, double amount) { - withdraw(currency.getKey(), amount); - } - - /** - * Withdraws the amount from the wallet. - *

- * Should not interact with the wallet directly, use the methods below. - *

- * - * @param key the key - * @param amount the amount - */ - default void withdraw(String key, double amount) { - getWallet().subtract(key, amount); - } - - /** - * Checks if the wallet has the amount. - *

- * Should not interact with the wallet directly, use the methods below. - *

- * - * @param currency the currency - * @param amount the amount - * @return true if the wallet has the amount - */ - default boolean has(Currency currency, double amount) { - return has(currency.getKey(), amount); - } - - /** - * Checks if the wallet has the amount. - *

- * Should not interact with the wallet directly, use the methods below. - *

- * - * @param key the key - * @param amount the amount - * @return true if the wallet has the amount - */ - default boolean has(String key, double amount) { - return getWallet().has(key, amount); - } - - /** - * Gets the balance of the wallet. - *

- * Should not interact with the wallet directly, use the methods below. - *

- * - * @param currency the currency - * @return the balance - */ - default double getBalance(Currency currency) { - return getBalance(currency.getKey()); - } - - /** - * Gets the balance of the wallet. - *

- * Should not interact with the wallet directly, use the methods below. - *

- * - * @param key the key - * @return the balance - */ - default double getBalance(String key) { - return getWallet().balance(key); - } - - /** - * Sets the balance of the wallet. - *

- * Should not interact with the wallet directly, use the methods below. - *

- * - * @param currency the currency - * @param amount the amount - */ - default void setBalance(Currency currency, double amount) { - setBalance(currency.getKey(), amount); - } - - /** - * Sets the balance of the wallet. - *

- * Should not interact with the wallet directly, use the methods below. - *

- * - * @param key the key - * @param amount the amount - */ - default void setBalance(String key, double amount) { - getWallet().put(key, amount); - } - - /** - * Resets the wallet to the initial balance. - *

- * Should not interact with the wallet directly, use the methods below. - *

- * - * @param currency the currency - */ - default void reset(Currency currency) { - setBalance(currency.getKey(), currency.getInitialBalance()); - } - /** * Retrieves the UUID of the WalletOwner. * * @return the UUID */ - default UUID getUniqueId() { - return blobCrudable().getUUID(); + default String getIdentification() { + return blobCrudable().getIdentification(); } } \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/entities/currency/WalletOwnerManager.java b/src/main/java/us/mytheria/bloblib/entities/currency/WalletOwnerManager.java index 9c43b086..082f1b7e 100644 --- a/src/main/java/us/mytheria/bloblib/entities/currency/WalletOwnerManager.java +++ b/src/main/java/us/mytheria/bloblib/entities/currency/WalletOwnerManager.java @@ -1,7 +1,6 @@ package us.mytheria.bloblib.entities.currency; -import me.anjoismysign.anjo.crud.SQLCrudManager; -import me.anjoismysign.anjo.entities.NamingConventions; +import net.milkbowl.vault.economy.IdentityEconomy; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.Event; @@ -17,24 +16,23 @@ import us.mytheria.bloblib.managers.BlobPlugin; import us.mytheria.bloblib.managers.Manager; import us.mytheria.bloblib.managers.ManagerDirector; -import us.mytheria.bloblib.utilities.BlobCrudManagerBuilder; +import us.mytheria.bloblib.storage.BlobCrudManager; +import us.mytheria.bloblib.storage.IdentifierType; +import us.mytheria.bloblib.storage.StorageType; +import us.mytheria.bloblib.utilities.BlobCrudManagerFactory; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Optional; -import java.util.UUID; +import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; import java.util.function.Function; +import java.util.stream.Collectors; public class WalletOwnerManager extends Manager implements Listener { - protected final HashMap owners; - private HashSet saving; - protected SQLCrudManager sqlCrudManager; + protected final HashMap walletOwners; + private final HashSet saving; + protected BlobCrudManager crudManager; private final BlobPlugin plugin; - private final Function walletOwner; - private final Function newBorn; - private final boolean logActivity; - private final String crudableName; + private final Function generator; private final @Nullable Function joinEvent; private final @Nullable Function quitEvent; private boolean registeredEconomy, registeredPAPI; @@ -42,24 +40,24 @@ public class WalletOwnerManager extends Manager implement protected ObjectDirector currencyDirector; @Nullable private EconomyPHExpansion economyPHExpansion; + private HashMap implementations; - protected WalletOwnerManager(ManagerDirector managerDirector, Function newBorn, - Function walletOwner, + protected WalletOwnerManager(ManagerDirector managerDirector, Function newBorn, + Function generator, String crudableName, boolean logActivity, @Nullable Function joinEvent, @Nullable Function quitEvent) { super(managerDirector); plugin = managerDirector.getPlugin(); Bukkit.getPluginManager().registerEvents(this, plugin); - owners = new HashMap<>(); - this.newBorn = newBorn; - this.walletOwner = walletOwner; - this.logActivity = logActivity; - this.crudableName = NamingConventions.toPascalCase(crudableName); + walletOwners = new HashMap<>(); + this.generator = generator; this.joinEvent = joinEvent; this.quitEvent = quitEvent; this.registeredEconomy = false; - reload(); + saving = new HashSet<>(); + crudManager = BlobCrudManagerFactory.PLAYER(plugin, crudableName, newBorn, logActivity); + saveAll(); } @Override @@ -70,15 +68,7 @@ public void unload() { @Override public void reload() { unload(); - saving = new HashSet<>(); - boolean useSQLite = getPlugin().getConfig().getBoolean("Database.UseSQLite"); - if (useSQLite) { - sqlCrudManager = BlobCrudManagerBuilder.SQLITE_UUID(plugin, "UUID", 36, - crudableName, newBorn, logActivity); - } else { - sqlCrudManager = BlobCrudManagerBuilder.MYSQL_UUID(plugin, "UUID", 36, - crudableName, newBorn, logActivity); - } + Bukkit.getScheduler().runTaskAsynchronously(getPlugin(), this::updateImplementations); } @EventHandler @@ -92,19 +82,23 @@ public void onPrejoin(AsyncPlayerPreLoginEvent e) { public void onJoin(PlayerJoinEvent e) { Player player = e.getPlayer(); UUID uuid = player.getUniqueId(); - CompletableFuture future = new CompletableFuture<>(); + CompletableFuture future = new CompletableFuture<>(); Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { - if (sqlCrudManager.exists(uuid.toString())) { - owners.put(uuid, walletOwner.apply(sqlCrudManager.read(uuid.toString()))); + if (player == null || !player.isOnline()) { + future.completeExceptionally(new NullPointerException("Player is null")); return; } - T walletOwner = this.walletOwner.apply(sqlCrudManager.createAndRegister(uuid.toString())); - owners.put(uuid, walletOwner); - future.complete(walletOwner); + BlobCrudable crudable = crudManager.read(uuid.toString()); + future.complete(crudable); }); - future.thenAccept(walletOwner -> { - if (joinEvent != null) - Bukkit.getPluginManager().callEvent(joinEvent.apply(walletOwner)); + future.thenAccept(crudable -> { + if (player == null || !player.isOnline()) + return; + T applied = generator.apply(crudable); + walletOwners.put(uuid, applied); + if (joinEvent == null) + return; + Bukkit.getPluginManager().callEvent(joinEvent.apply(applied)); }); } @@ -120,53 +114,108 @@ public void onQuit(PlayerQuitEvent e) { Bukkit.getPluginManager().callEvent(quitEvent.apply(walletOwner)); saving.add(uuid); Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { - sqlCrudManager.update(walletOwner.serializeAllAttributes()); + crudManager.update(walletOwner.serializeAllAttributes()); + removeObject(uuid); saving.remove(uuid); }); } - public void addObject(T walletOwner) { - owners.put(walletOwner.getUniqueId(), walletOwner); + public void addObject(UUID key, T walletOwner) { + walletOwners.put(key, walletOwner); + } + + public void addObject(Player player, T walletOwner) { + addObject(player.getUniqueId(), walletOwner); } - public void removeObject(T lumber) { - owners.remove(lumber.getUniqueId()); + public void removeObject(UUID key) { + walletOwners.remove(key); + } + + public void removeObject(Player player) { + removeObject(player.getUniqueId()); } public Optional isWalletOwner(UUID uuid) { - return Optional.ofNullable(owners.get(uuid)); + return Optional.ofNullable(walletOwners.get(uuid)); } public Optional isWalletOwner(Player player) { return isWalletOwner(player.getUniqueId()); } + public void ifIsOnline(UUID uuid, Consumer consumer) { + Optional optional = isWalletOwner(uuid); + optional.ifPresent(consumer); + } + + public void ifIsOnlineThenUpdateElse(UUID uuid, Consumer consumer, + Runnable runnable) { + Optional optional = isWalletOwner(uuid); + boolean isPresent = optional.isPresent(); + if (isPresent) { + T walletOwner = optional.get(); + consumer.accept(walletOwner); + crudManager.update(walletOwner.serializeAllAttributes()); + } else { + runnable.run(); + } + } + + public void ifIsOnlineThenUpdate(UUID uuid, Consumer consumer) { + ifIsOnlineThenUpdateElse(uuid, consumer, () -> { + }); + } + private void saveAll() { - owners.values().forEach(walletOwner -> sqlCrudManager.update(walletOwner.serializeAllAttributes())); + walletOwners.values().forEach(walletOwner -> crudManager.update(walletOwner.serializeAllAttributes())); } - protected CompletableFuture read(UUID uuid) { + public CompletableFuture readAsynchronously(String key) { CompletableFuture future = new CompletableFuture<>(); Bukkit.getScheduler().runTaskAsynchronously(getPlugin(), () -> - future.complete(walletOwner.apply(sqlCrudManager.read(uuid.toString())))); + future.complete(generator.apply(crudManager.read(key)))); return future; } + public void readThenUpdate(String key, Consumer consumer) { + T walletOwner = generator.apply(crudManager.read(key)); + consumer.accept(walletOwner); + crudManager.update(walletOwner.serializeAllAttributes()); + } + + public boolean exists(String key) { + return crudManager.exists(key); + } + + public Collection getAll() { + return walletOwners.values(); + } + /** * Registers the BlobEconomy for this plugin. * - * @param defaultCurrency The default currency for this economy. + * @param defaultCurrency The default currency for this economy. + * @param currencyDirector The director for the currencies. + * @param force if true, will override vault economy providers * @return The provider for the Economy class in case you * later want to use it. */ @NotNull - public BlobEconomy registerEconomy(Currency defaultCurrency, ObjectDirector currencyDirector) { + public BlobEconomy registerEconomy(Currency defaultCurrency, + ObjectDirector currencyDirector, + boolean force) { if (registeredEconomy) throw new IllegalStateException("BlobPlugin already registered their BlobEconomy"); registeredEconomy = true; - this.defaultCurrency = defaultCurrency.getKey(); + this.defaultCurrency = Objects.requireNonNull(defaultCurrency).getKey(); this.currencyDirector = currencyDirector; - return new BlobEconomy<>(this); + return new BlobEconomy<>(this, force); + } + + public BlobEconomy registerEconomy(Currency defaultCurrency, + ObjectDirector currencyDirector) { + return registerEconomy(defaultCurrency, currencyDirector, true); } @Nullable @@ -222,11 +271,18 @@ public EconomyPHExpansion registerPlaceholderAPIExpansion() { throw new IllegalStateException("BlobPlugin already registered their PlaceholderAPI expansion"); if (!registeredEconomy) throw new IllegalStateException("BlobPlugin has not registered their BlobEconomy"); - if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") == null) + if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") == null) { + getPlugin().getAnjoLogger().log("PlaceholderAPI not found, not registering PlaceholderAPI expansion for " + getPlugin().getName()); return null; + } registeredPAPI = true; EconomyPHExpansion expansion = new EconomyPHExpansion<>(this); - expansion.register(); + CompletableFuture future = new CompletableFuture<>(); + Bukkit.getScheduler().runTask(getPlugin(), () -> { + expansion.register(); + future.complete(null); + }); + future.join(); economyPHExpansion = expansion; return expansion; } @@ -239,4 +295,57 @@ public WalletOwnerManager unregisterPlaceholderAPIExpansion() { registeredPAPI = false; return this; } + + /** + * Will get an implementation of the CurrencyEconomy class. + * If the implementation doesn't exist, it will + * warn through console and return null. + * + * @param key The key of the implementation. + * @return The implementation. + */ + @Nullable + public CurrencyEconomy getImplementation(String key) { + CurrencyEconomy economy = implementations.get(key); + if (economy == null) { + getPlugin().getAnjoLogger().singleError("Implementation doesn't exist. Currently available " + + "implementations: " + Arrays.toString(implementations.values().stream() + .map(CurrencyEconomy::getName) + .toArray())); + } + return economy; + } + + public Collection retrieveImplementations() { + return implementations.values() + .stream() + .map(currencyEconomy -> (IdentityEconomy) currencyEconomy) + .collect(Collectors.toList()); + } + + private HashMap convertAll() { + HashMap map = new HashMap<>(); + currencyDirector.getObjectManager().values().stream() + .map(currency -> new CurrencyEconomy(currency, this)) + .forEach(currencyEconomy -> map.put(currencyEconomy.getName(), currencyEconomy)); + return map; + } + + /** + * Updates the implementations of the BlobEconomy. + */ + protected void updateImplementations() { + if (!registeredEconomy) { + throw new IllegalStateException("BlobPlugin has not registered their BlobEconomy"); + } + this.implementations = convertAll(); + } + + public StorageType getStorageType() { + return crudManager.getStorageType(); + } + + public IdentifierType getIdentifierType() { + return crudManager.getIdentifierType(); + } } diff --git a/src/main/java/us/mytheria/bloblib/entities/display/DisplayBuilder.java b/src/main/java/us/mytheria/bloblib/entities/display/DisplayBuilder.java new file mode 100644 index 00000000..78fa9daa --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/display/DisplayBuilder.java @@ -0,0 +1,365 @@ +package us.mytheria.bloblib.entities.display; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.BlockDisplay; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.ItemDisplay; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.util.Transformation; +import org.jetbrains.annotations.Nullable; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +public record DisplayBuilder(@Nullable ItemStack itemStack, + @Nullable BlockData blockData, + Vector3f scale, + Quaternionf leftRotation, + Quaternionf rightRotation, + Vector3f translation) { + /** + * Creates a new DisplayBuilder with the given BlockData. + * + * @param blockData the BlockData + * @return the DisplayBuilder + */ + public static DisplayBuilder of(BlockData blockData) { + if (blockData == null) + throw new IllegalArgumentException("BlockData cannot be null!"); + return new DisplayBuilder(null, + null, + new Vector3f(1, 1, 1), + new Quaternionf(0, 0, 0, 1), + new Quaternionf(0, 0, 0, 1), + new Vector3f(0.5f, 0, 0.5f)); + } + + /** + * Creates a new DisplayBuilder with the given Material. + * + * @param material the Material + * @return the DisplayBuilder + */ + public static DisplayBuilder of(Material material) { + return of(new ItemStack(material)); + } + + /** + * Creates a new DisplayBuilder with the given ItemStack. + * + * @param itemStack the ItemStack + * @return the DisplayBuilder + */ + public static DisplayBuilder of(ItemStack itemStack) { + if (itemStack == null) + throw new IllegalArgumentException("ItemStack cannot be null!"); + if (itemStack.getType() == Material.AIR) + throw new IllegalArgumentException("ItemStack cannot be AIR!"); + return new DisplayBuilder(itemStack, + null, + new Vector3f(1, 1, 1), + new Quaternionf(0, 0, 0, 1), + new Quaternionf(0, 0, 0, 1), + new Vector3f(0.5f, 0, 0.5f)); + } + + /** + * Updates the ItemStack + * + * @param display the new ItemStack + * @return the DisplayBuilder + */ + public DisplayBuilder setItemStack(ItemStack display) { + if (display == null) + throw new IllegalArgumentException("ItemStack cannot be null!"); + if (display.getType() == Material.AIR) + throw new IllegalArgumentException("ItemStack cannot be AIR!"); + return new DisplayBuilder(display, blockData, scale, leftRotation, rightRotation, translation); + } + + /** + * Updates the BlockData + * + * @param display the new BlockData + * @return the DisplayBuilder + */ + public DisplayBuilder setBlockData(BlockData display) { + if (display == null) + throw new IllegalArgumentException("BlockData cannot be null!"); + return new DisplayBuilder(itemStack, display, scale, leftRotation, rightRotation, translation); + } + + /** + * Will uniformily scale the display. + * It's the same as calling {@link #scale(float, float, float)} with the same value for x, y and z. + * + * @param scale the scale (note that '1.0f' as the reference for the original + * block/falling block size) + * @return the DisplayBuilder + */ + public DisplayBuilder uniformScale(float scale) { + return new DisplayBuilder(itemStack, blockData, + new Vector3f(scale, scale, scale), + leftRotation, rightRotation, translation); + } + + /** + * Will scale the display. + * (note that '1.0f' as the reference for the original + * block/falling block size) + * + * @param x the x scale + * @param y the y scale + * @param z the z scale + * @return the DisplayBuilder + */ + public DisplayBuilder scale(float x, float y, float z) { + return new DisplayBuilder(itemStack, blockData, + new Vector3f(x, y, z), + leftRotation, rightRotation, translation); + } + + /** + * Will translate/move the display. + * (halving the scale will center the display) + * + * @param x the x translation + * @param y the y translation + * @param z the z translation + * @return the DisplayBuilder + */ + public DisplayBuilder translate(float x, float y, float z) { + return new DisplayBuilder(itemStack, blockData, scale, + leftRotation, rightRotation, new Vector3f(x, y, z)); + } + + /** + * Will translate/move the display vertically / on the y-axis. + * Will keep previous x and z translation values. + * + * @param y the y translation + * @return the DisplayBuilder + */ + public DisplayBuilder translateVertical(float y) { + return new DisplayBuilder(itemStack, blockData, scale, + leftRotation, rightRotation, + new Vector3f(translation.x, y, translation.z)); + } + + /** + * Will translate/move the display horizontally / on the x and z axis. + * In other words, will center the display while staying in the same + * Y coordinate in game. + * + * @return the DisplayBuilder + */ + public DisplayBuilder centerHorizontal() { + return new DisplayBuilder(itemStack, blockData, scale, + leftRotation, rightRotation, + new Vector3f(scale.x / 2, 0, scale.z / 2)); + } + + /** + * Will center the display, ignoring the y-axis. + * + * @return the DisplayBuilder + */ + public DisplayBuilder center() { + return new DisplayBuilder(itemStack, blockData, scale, + leftRotation, rightRotation, + new Vector3f(scale.x / 2, scale.y / 2, scale.z / 2)); + } + + /** + * Will manipulate the leftRotation while keeping a 1:1 (1.0f) scale. + * + * @param x the x rotation + * @param y the y rotation + * @param z the z rotation + * @return the DisplayBuilder + */ + public DisplayBuilder rotateLeft(float x, float y, float z) { + return rotateLeft(x, y, z, 1); + } + + /** + * Will manipulate the leftRotation + * + * @param x the x rotation + * @param y the y rotation + * @param z the z rotation + * @param scalar the scalar + * @return the DisplayBuilder + */ + public DisplayBuilder rotateLeft(float x, float y, float z, float scalar) { + return new DisplayBuilder(itemStack, blockData, this.scale, + new Quaternionf(x, y, z, scalar), rightRotation, translation); + } + + /** + * Will manipulate the rightRotation while keeping a 1:1 (1.0f) scale. + * + * @param x the x rotation + * @param y the y rotation + * @param z the z rotation + * @return the DisplayBuilder + */ + public DisplayBuilder rotateRight(float x, float y, float z) { + return rotateRight(x, y, z, 1); + } + + /** + * Will manipulate the rightRotation + * + * @param x the x rotation + * @param y the y rotation + * @param z the z rotation + * @param scalar the scalar + * @return the DisplayBuilder + */ + public DisplayBuilder rotateRight(float x, float y, float z, float scalar) { + return new DisplayBuilder(itemStack, blockData, this.scale, + leftRotation, new Quaternionf(x, y, z, scalar), translation); + } + + private Transformation transformation() { + return new Transformation(scale, leftRotation, scale, rightRotation); + } + + /** + * Will spawn an ItemDisplay at the given location + * + * @param location the spawn location + * @return the ItemDisplay + */ + public ItemDisplay toItemDisplay(Location location) { + if (itemStack == null) + throw new IllegalArgumentException("ItemStack cannot be null!"); + if (itemStack.getType() == Material.AIR) + throw new IllegalArgumentException("ItemStack cannot be AIR!"); + + Entity entity = location.getWorld().spawnEntity(location, + EntityType.ITEM_DISPLAY); + ItemDisplay itemDisplay = (ItemDisplay) entity; + itemDisplay.setItemStack(itemStack); + itemDisplay.setTransformation(transformation()); + return itemDisplay; + } + + /** + * Will spawn an ItemDisplay at the given location + * and return a DisplayDecorator for it instead + * of the ItemDisplay itself. + * + * @param location the spawn location + * @return the DisplayDecorator + */ + public DisplayDecorator toItemDisplayDecorator(Location location, + JavaPlugin plugin) { + return new DisplayDecorator<>(toItemDisplay(location), plugin); + } + + /** + * Will spawn an ItemDisplay at the given location. + * The ItemDisplay will be transformed according to the + * given ItemDisplayTransform. + * + * @param location the spawn location + * @return the ItemDisplay + */ + public ItemDisplay toItemDisplay(Location location, + ItemDisplay.ItemDisplayTransform transform) { + if (itemStack == null) + throw new IllegalArgumentException("ItemStack cannot be null!"); + if (itemStack.getType() == Material.AIR) + throw new IllegalArgumentException("ItemStack cannot be AIR!"); + + Entity entity = location.getWorld().spawnEntity(location, + EntityType.ITEM_DISPLAY); + ItemDisplay itemDisplay = (ItemDisplay) entity; + itemDisplay.setItemStack(itemStack); + itemDisplay.setTransformation(transformation()); + itemDisplay.setItemDisplayTransform(transform); + return itemDisplay; + } + + /** + * Will spawn an ItemDisplay at the given location + * and return a DisplayDecorator for it instead + * of the ItemDisplay itself. + * The ItemDisplay will be transformed according to the + * given ItemDisplayTransform. + * + * @param location the spawn location + * @return the DisplayDecorator + */ + public DisplayDecorator toItemDisplayDecorator(Location location, + ItemDisplay.ItemDisplayTransform transform, + JavaPlugin plugin) { + return new DisplayDecorator<>(toItemDisplay(location, transform), plugin); + } + + /** + * Will spawn a BlockDisplay at the given location + * using the cached BlockData. + * + * @param location the spawn location + * @return the BlockDisplay + */ + public BlockDisplay toBlockDisplay(Location location) { + return toBlockDisplay(location, null); + } + + /** + * Will spawn a BlockDisplay at the given location + * providing a fallback BlockData in case the + * cached BlockData is null. + * + * @param location the spawn location + * @return the BlockDisplay + */ + public BlockDisplay toBlockDisplay(Location location, @Nullable BlockData display) { + BlockData data = this.blockData == null ? display : this.blockData; + if (data == null) + throw new IllegalArgumentException("BlockData not set"); + Entity entity = location.getWorld().spawnEntity(location, + EntityType.BLOCK_DISPLAY); + BlockDisplay blockDisplay = (BlockDisplay) entity; + blockDisplay.setBlock(data); + blockDisplay.setTransformation(transformation()); + return blockDisplay; + } + + /** + * Will spawn a BlockDisplay at the given location + * using the cached BlockData. + * Will return a DisplayDecorator for it instead + * of the BlockDisplay itself. + * + * @param location the spawn location + * @return the DisplayDecorator + */ + public DisplayDecorator toBlockDisplayDecorator(Location location, + JavaPlugin plugin) { + return toBlockDisplayDecorator(location, null, plugin); + } + + /** + * Will spawn a BlockDisplay at the given location + * providing a fallback BlockData in case the + * cached BlockData is null. + * Will return a DisplayDecorator for it instead + * of the BlockDisplay itself. + * + * @param location the spawn location + * @return the DisplayDecorator + */ + public DisplayDecorator toBlockDisplayDecorator(Location location, + BlockData display, + JavaPlugin plugin) { + return new DisplayDecorator<>(toBlockDisplay(location, display), plugin); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/display/DisplayData.java b/src/main/java/us/mytheria/bloblib/entities/display/DisplayData.java new file mode 100644 index 00000000..bb6a4aaf --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/display/DisplayData.java @@ -0,0 +1,126 @@ +package us.mytheria.bloblib.entities.display; + +import org.bukkit.Color; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Display; +import org.jetbrains.annotations.Nullable; + +import java.io.File; + +/** + * Holds all data that DisplayEntities hold except + * data related to Transformations. + * + * @param brightness + * @param billboard + * @param shadowRadius + * @param shadowStrength + */ +public record DisplayData(Display.Brightness brightness, + Display.Billboard billboard, + float shadowRadius, + float shadowStrength, + float viewRange, + float displayWidth, + float displayHeight, + @Nullable Color glowColorOverride, + boolean applyBrightness) { + + public static DisplayData DEFAULT = new DisplayData(new Display.Brightness(15, 15), + Display.Billboard.FIXED, 0.0f, 1.0f, + 1.0f, 0.0f, 0.0f, + null, false); + + /** + * Will apply the DisplayData to the given Display. + * + * @param display the Display to apply the DisplayData to + */ + public void apply(Display display) { + if (applyBrightness) + display.setBrightness(brightness); + display.setBillboard(billboard); + display.setShadowRadius(shadowRadius); + display.setShadowStrength(shadowStrength); + display.setViewRange(viewRange); + display.setDisplayWidth(displayWidth); + display.setDisplayHeight(displayHeight); + display.setGlowColorOverride(glowColorOverride); + } + + /** + * Will read the Display-Data section from the given ConfigurationSection. + * If ConfigurationSection is missing the Brightness or Billboard values, + * will fail fast. Regarding the other values, default values will be + * provided if missing. + * + * @param section the ConfigurationSection to read from + * @return the DisplayData from the given ConfigurationSection + */ + public static DisplayData of(ConfigurationSection section) { + Display.Brightness brightness = DisplayReader.BRIGHTNESS_DEFAULT(section); + Display.Billboard billboard = DisplayReader.BILLBOARD_DEFAULT(section); + float shadowRadius = (float) section.getDouble("Shadow-Radius", 0.0); + float shadowStrength = (float) section.getDouble("Shadow-Strength", 1.0); + float viewRange = (float) section.getDouble("View-Range", 1.0); + float displayWidth = (float) section.getDouble("Display-Width", 0.0); + float displayHeight = (float) section.getDouble("Display-Height", 0.0); + boolean applyBrightness = section.getBoolean("Apply-Brightness", false); + Color color = null; + if (section.isString("Glow-Color-Override")) { + String rgb = section.getString("Glow-Color-Override"); + String[] split = rgb.split(","); + if (split.length == 1) + throw new IllegalArgumentException("Glow-Color-Override is not in the correct format"); + int r = Integer.parseInt(split[0]); + int g = Integer.parseInt(split[1]); + int b = Integer.parseInt(split[2]); + color = Color.fromRGB(r, g, b); + } + return new DisplayData(brightness, billboard, shadowRadius, shadowStrength, + viewRange, displayWidth, displayHeight, color, applyBrightness); + } + + /** + * Will read the Display-Data section from the given FileConfiguration. + * If FileConfiguration is missing the Display-Data section, + * will fail fast. + * + * @param configuration the FileConfiguration to read from + * @return the DisplayData from the given FileConfiguration + */ + public static DisplayData of(FileConfiguration configuration) { + if (!configuration.isConfigurationSection("Display-Data")) + throw new IllegalArgumentException("Display-Data section is missing"); + return of(configuration.getConfigurationSection("Display-Data")); + } + + /** + * Will read the Display-Data section from the given File. + * If file is not a .yml file, will fail fast. + * + * @param file the File to read from + * @return the DisplayData from the given File + */ + public static DisplayData of(File file) { + String name = file.getName(); + if (!name.endsWith(".yml")) + throw new IllegalArgumentException("File is not a .yml file"); + return of(YamlConfiguration.loadConfiguration(file)); + } + + public void write(ConfigurationSection section) { + DisplayWriter.WRITE(section, brightness, applyBrightness); + DisplayWriter.WRITE(section, billboard); + section.set("Shadow-Radius", shadowRadius); + section.set("Shadow-Strength", shadowStrength); + section.set("View-Range", viewRange); + section.set("Display-Width", displayWidth); + section.set("Display-Height", displayHeight); + if (glowColorOverride != null) + section.set("Glow-Color-Override", glowColorOverride.getRed() + "," + + glowColorOverride.getGreen() + "," + glowColorOverride.getBlue()); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/display/DisplayDecorator.java b/src/main/java/us/mytheria/bloblib/entities/display/DisplayDecorator.java new file mode 100644 index 00000000..4e349711 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/display/DisplayDecorator.java @@ -0,0 +1,329 @@ +package us.mytheria.bloblib.entities.display; + +import org.bukkit.entity.Display; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.scheduler.BukkitTask; +import org.bukkit.util.Transformation; +import org.joml.AxisAngle4f; + +import java.util.List; +import java.util.function.Function; + +public class DisplayDecorator { + private final T decorated; + private final Plugin plugin; + private TransformationStepMemory stepMemory; + private BukkitTask perpetualTask; + + /** + * Will create a new display decorator. + * + * @param display the display to wrap. + */ + public DisplayDecorator(T display, Plugin plugin) { + this.decorated = display; + this.plugin = plugin; + this.stepMemory = new TransformationStepMemory<>(plugin); + } + + /** + * Will return the wrapped display. + * + * @return the wrapped display. + */ + public T call() { + return decorated; + } + + /** + * Will return the last step in the memory. + */ + public TransformationStep peekLastStep() { + return stepMemory.deque.peekLast(); + } + + /** + * Will add provided step as last step in the memory. + * If clock had not started, it will start it. + * + * @param step the step to add. + */ + public void learnStep(TransformationStep step) { + stepMemory.addLast(step); + stepMemory.startClock(call()); + } + + /** + * Will add provided steps as last steps in the memory. + * If clock had not started, it will start it. + * + * @param steps the steps to add. + */ + public void learnSteps(List steps) { + stepMemory.addLast(steps); + stepMemory.startClock(call()); + } + + /** + * Will stop the transformation/animation clock + * using the next transformation step in the memory. + * Will loc + */ + public void stopClock() { + stepMemory.stopClock(call()); + stepMemory = new TransformationStepMemory<>(plugin); + } + + /** + * Will stop the transformation/animation clock + * + * @param step the step to stop at + */ + public void stopClock(TransformationStep step) { + stepMemory.stopClock(step, call()); + } + + /** + * Will transform the display to leftRotation. + * Will use '-1' as the interpolation delay. + * + * @param x the x-axis of the rotation. + * @param y the y-axis of the rotation. + * @param z the z-axis of the rotation. + * @param degrees the degrees of the rotation (in degrees, not radians). + * @param interpolationDuration the duration of the interpolation. + */ + public void transformLeft(float x, float y, float z, float degrees, + int interpolationDuration) { + transformLeft(x, y, z, degrees, interpolationDuration, -1); + } + + /** + * Will transform the display to leftRotation. + * + * @param x the x-axis of the rotation. + * @param y the y-axis of the rotation. + * @param z the z-axis of the rotation. + * @param degrees the degrees of the rotation (in degrees, not radians). + * @param interpolationDuration the duration of the interpolation. + * @param interpolationDelay the delay of the interpolation. + */ + public void transformLeft(float x, float y, float z, float degrees, + int interpolationDuration, + int interpolationDelay) { + degrees = (float) Math.toRadians(degrees); + decorated.setInterpolationDuration(interpolationDuration); + decorated.setInterpolationDelay(interpolationDelay); + Transformation transformation = decorated.getTransformation(); + transformation.getLeftRotation() + .set(new AxisAngle4f(degrees, x, y, z)); + decorated.setTransformation(transformation); + } + + /** + * Will transform the display to leftRotation. + * Will use '-1' as the interpolation delay. + * + * @param x the x-axis of the rotation. + * @param y the y-axis of the rotation. + * @param z the z-axis of the rotation. + * @param degrees the degrees of the rotation (in degrees, not radians). + * @param interpolationDuration the duration of the interpolation. + */ + public void transformRight(float x, float y, float z, float degrees, + int interpolationDuration) { + transformRight(x, y, z, degrees, interpolationDuration, -1); + } + + /** + * Will transform the display to rightRotation. + * + * @param x the x-axis of the rotation. + * @param y the y-axis of the rotation. + * @param z the z-axis of the rotation. + * @param degrees the degrees of the rotation (in degrees, not radians). + * @param interpolationDuration the duration of the interpolation. + * @param interpolationDelay the delay of the interpolation. + */ + public void transformRight(float x, float y, float z, float degrees, + int interpolationDuration, + int interpolationDelay) { + degrees = (float) Math.toRadians(degrees); + decorated.setInterpolationDuration(interpolationDuration); + decorated.setInterpolationDelay(interpolationDelay); + Transformation transformation = decorated.getTransformation(); + transformation.getRightRotation() + .set(new AxisAngle4f(degrees, x, y, z)); + decorated.setTransformation(transformation); + } + + /** + * Will set a perpetual task asynchronously. + * If there is already a perpetual task, it will cancel it + * before setting the new one. + * + * @param plugin the plugin used to schedule the task. + * @param delay the delay. + * @param period the period. + * @param runnable the runnable. + */ + public void setPerpetualAsync(Plugin plugin, long delay, + long period, Runnable runnable) { + if (perpetualTask != null) { + perpetualTask.cancel(); + } + perpetualTask = new BukkitRunnable() { + @Override + public void run() { + if (!decorated.isValid()) { + cancel(); + return; + } + runnable.run(); + } + }.runTaskTimerAsynchronously(plugin, delay, period); + } + + /** + * Will set a perpetual task synchronously. + * If there is already a perpetual task, it will cancel it + * before setting the new one. + * + * @param plugin the plugin used to schedule the task. + * @param delay the delay. + * @param period the period. + * @param runnable the runnable. + */ + public void setPerpetual(Plugin plugin, long delay, + long period, Runnable runnable) { + if (perpetualTask != null) { + perpetualTask.cancel(); + } + perpetualTask = new BukkitRunnable() { + @Override + public void run() { + if (!decorated.isValid()) { + cancel(); + return; + } + runnable.run(); + } + }.runTaskTimer(plugin, delay, period); + } + + /** + * Will transform the display to leftRotation + * perpetually using asynchronous tasks. + * + * @param x the x-axis of the rotation. + * @param y the y-axis of the rotation. + * @param z the z-axis of the rotation. + * @param degrees the degrees of the rotation (in degrees, not radians). + * @param interpolationDuration the duration of the interpolation. + * @param plugin the plugin used to schedule the task + */ + public void transformLeftPerpetual(float x, float y, float z, float degrees, + int interpolationDuration, + Plugin plugin) { + setPerpetualAsync(plugin, -1, interpolationDuration, () -> transformRight(x, y, z, degrees, interpolationDuration)); + } + + /** + * Will transform the display to leftRotation + * perpetually using asynchronous tasks. + * + * @param x the x-axis of the rotation. + * @param y the y-axis of the rotation. + * @param z the z-axis of the rotation. + * @param degrees the degrees of the rotation (in degrees, not radians). + * @param interpolationDuration the duration of the interpolation. + * @param interpolationDelay the delay of the interpolation. + * @param plugin the plugin used to schedule the task + */ + public void transformLeftPerpetual(float x, float y, float z, float degrees, + int interpolationDuration, + int interpolationDelay, + Plugin plugin) { + setPerpetualAsync(plugin, -1, interpolationDuration, () -> transformLeft(x, y, z, degrees, interpolationDuration, + interpolationDelay)); + } + + /** + * Will transform the display to rightRotation + * perpetually using asynchronous tasks. + * + * @param x the x-axis of the rotation. + * @param y the y-axis of the rotation. + * @param z the z-axis of the rotation. + * @param degrees the degrees of the rotation (in degrees, not radians). + * @param interpolationDuration the duration of the interpolation. + * @param plugin the plugin used to schedule the task + */ + public void transformRightPerpetual(float x, float y, float z, float degrees, + int interpolationDuration, + Plugin plugin) { + setPerpetualAsync(plugin, -1, interpolationDuration, () -> transformRight(x, y, z, degrees, interpolationDuration)); + } + + /** + * Will transform the display to rightRotation + * perpetually using asynchronous tasks. + * + * @param x the x-axis of the rotation. + * @param y the y-axis of the rotation. + * @param z the z-axis of the rotation. + * @param degrees the degrees of the rotation (in degrees, not radians). + * @param interpolationDuration the duration of the interpolation. + * @param interpolationDelay the delay of the interpolation. + * @param plugin the plugin used to schedule the task + */ + public void transformRightPerpetual(float x, float y, float z, float degrees, + int interpolationDuration, + int interpolationDelay, + Plugin plugin) { + setPerpetualAsync(plugin, -1, interpolationDuration, () -> transformRight(x, y, z, degrees, interpolationDuration, + interpolationDelay)); + } + + /** + * Will open the TransformationStepFactory passing the current transformation + * + * @param interpolationDuration the duration of the interpolation (in ticks). + * @return the TransformationBuilder + */ + public TransformationStepFactory manufacture(int interpolationDuration) { + T call = call(); + return new TransformationStepFactory( + new TransformationStep(call.getTransformation(), + call.getShadowRadius(), + call.getShadowStrength(), + interpolationDuration)); + } + + /** + * Will open the TransformationStepFactory passing the current transformation + * Will use the default interpolation duration of 20 ticks (1 second). + * + * @return the TransformationBuilder + */ + public TransformationStepFactory manufacture() { + return manufacture(20); + } + + /** + * public TransformationStepFactory manufacture() { + * return manufacture(20); + * } + *

+ * /** + * Will set a perpetual transformation that was + * provided using asynchronous tasks. + * + * @param function the function that will provide the transformation. + * @param plugin the plugin used to schedule the task. + */ + public void setPerpetualTransformation(Function function, Plugin plugin) { + setPerpetualAsync(plugin, -1, call().getInterpolationDuration(), () -> decorated.setTransformation(function.apply(manufacture()))); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/display/DisplayReader.java b/src/main/java/us/mytheria/bloblib/entities/display/DisplayReader.java new file mode 100644 index 00000000..cce050e4 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/display/DisplayReader.java @@ -0,0 +1,78 @@ +package us.mytheria.bloblib.entities.display; + +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Display; +import org.bukkit.util.Transformation; +import org.jetbrains.annotations.Nullable; +import org.joml.Quaternionf; +import org.joml.Vector3f; +import us.mytheria.bloblib.entities.JOMLReader; + +public class DisplayReader { + public static Display.Brightness BRIGHTNESS_FAIL_FAST(ConfigurationSection section) { + if (!section.isInt("Block-Light")) + throw new IllegalArgumentException("Block-Light is not valid"); + if (!section.isInt("Sky-Light")) + throw new IllegalArgumentException("Sky-Light is not valid"); + return new Display.Brightness(section.getInt("Block-Light"), section.getInt("Sky-Light")); + } + + public static Display.Brightness BRIGHTNESS_DEFAULT(ConfigurationSection section) { + int blockLight = section.getInt("Block-Light", 15); + int skyLight = section.getInt("Sky-Light", 15); + return new Display.Brightness(blockLight, skyLight); + } + + @Nullable + public static Display.Brightness BRIGHTNESS_NULL(ConfigurationSection section) { + if (!section.isInt("Block-Light")) + return null; + if (!section.isInt("Sky-Light")) + return null; + return new Display.Brightness(section.getInt("Block-Light"), section.getInt("Sky-Light")); + } + + public static Display.Billboard BILLBOARD_FAIL_FAST(ConfigurationSection section) { + if (!section.isString("Billboard")) + throw new IllegalArgumentException("Billboard is not valid"); + return Display.Billboard.valueOf(section.getString("Billboard")); + } + + public static Display.Billboard BILLBOARD_DEFAULT(ConfigurationSection section) { + return Display.Billboard.valueOf(section.getString("Billboard", "FIXED")); + } + + @Nullable + public static Display.Billboard BILLBOARD_NULL(ConfigurationSection section) { + if (!section.isString("Billboard")) + return null; + return Display.Billboard.valueOf(section.getString("Billboard")); + } + + public static Transformation TRANSFORMATION_FAIL_FAST(ConfigurationSection section) { + Vector3f scale = JOMLReader.READ_VECTOR3F(section, "Scale"); + Vector3f translation = JOMLReader.READ_VECTOR3F(section, "Translation"); + Quaternionf leftRotation = JOMLReader.READ_QUATERNIONF(section, "Left-Rotation"); + Quaternionf rightRotation = JOMLReader.READ_QUATERNIONF(section, "Right-Rotation"); + return new Transformation(translation, leftRotation, scale, rightRotation); + } + + public static Transformation TRANSFORMATION_DEFAULT(ConfigurationSection section) { + Vector3f scale = new Vector3f((float) section.getDouble("Scale.X", 1), (float) section.getDouble("Scale.Y", 1), (float) section.getDouble("Scale.Z", 1)); + Vector3f translation = new Vector3f((float) section.getDouble("Translation.X", 0.5), (float) section.getDouble("Translation.Y", 0.5), (float) section.getDouble("Translation.Z", 0.5)); + Quaternionf leftRotation = new Quaternionf((float) section.getDouble("Left-Rotation.X", 0), (float) section.getDouble("Left-Rotation.Y", 0), (float) section.getDouble("Left-Rotation.Z", 0), (float) section.getDouble("Left-Rotation.W", 1)); + Quaternionf rightRotation = new Quaternionf((float) section.getDouble("Right-Rotation.X", 0), (float) section.getDouble("Right-Rotation.Y", 0), (float) section.getDouble("Right-Rotation.Z", 0), (float) section.getDouble("Right-Rotation.W", 1)); + return new Transformation(translation, leftRotation, scale, rightRotation); + } + + @Nullable + public static Transformation TRANSFORMATION_NULL(ConfigurationSection configuration) { + Transformation transformation; + try { + transformation = TRANSFORMATION_FAIL_FAST(configuration); + } catch (Exception e) { + return null; + } + return transformation; + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/display/DisplayWriter.java b/src/main/java/us/mytheria/bloblib/entities/display/DisplayWriter.java new file mode 100644 index 00000000..fd5dab62 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/display/DisplayWriter.java @@ -0,0 +1,64 @@ +package us.mytheria.bloblib.entities.display; + +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Display; +import org.bukkit.util.Transformation; +import org.joml.AxisAngle4f; + +public class DisplayWriter { + public static void WRITE(ConfigurationSection section, + Display.Brightness brightness, + boolean applyBrightness) { + section.set("Apply-Brightness", applyBrightness); + section.set("Block-Light", brightness.getBlockLight()); + section.set("Sky-Light", brightness.getSkyLight()); + } + + public static void WRITE(ConfigurationSection section, + Display.Billboard billboard) { + section.set("Billboard", billboard.name()); + } + + public static void WRITE(ConfigurationSection section, + Transformation transformation, + boolean useQuaternion) { + ConfigurationSection scaleSection = section.createSection("Scale"); + scaleSection.set("X", transformation.getScale().x); + scaleSection.set("Y", transformation.getScale().y); + scaleSection.set("Z", transformation.getScale().z); + ConfigurationSection translationSection = section.createSection("Translation"); + translationSection.set("X", transformation.getTranslation().x); + translationSection.set("Y", transformation.getTranslation().y); + translationSection.set("Z", transformation.getTranslation().z); + if (useQuaternion) { + ConfigurationSection leftRotationSection = section.createSection("Left-Rotation-Quaternion"); + leftRotationSection.set("X", transformation.getLeftRotation().x); + leftRotationSection.set("Y", transformation.getLeftRotation().y); + leftRotationSection.set("Z", transformation.getLeftRotation().z); + leftRotationSection.set("W", transformation.getLeftRotation().w); + ConfigurationSection rightRotationSection = section.createSection("Right-Rotation-Quaternion"); + rightRotationSection.set("X", transformation.getRightRotation().x); + rightRotationSection.set("Y", transformation.getRightRotation().y); + rightRotationSection.set("Z", transformation.getRightRotation().z); + rightRotationSection.set("W", transformation.getRightRotation().w); + } else { + AxisAngle4f leftRotation = new AxisAngle4f(transformation.getLeftRotation()); + ConfigurationSection leftRotationSection = section.createSection("Left-Rotation"); + leftRotationSection.set("X", leftRotation.x); + leftRotationSection.set("Y", leftRotation.y); + leftRotationSection.set("Z", leftRotation.z); + leftRotationSection.set("Angle", leftRotation.angle); + AxisAngle4f rightRotation = new AxisAngle4f(transformation.getRightRotation()); + ConfigurationSection rightRotationSection = section.createSection("Right-Rotation"); + rightRotationSection.set("X", rightRotation.x); + rightRotationSection.set("Y", rightRotation.y); + rightRotationSection.set("Z", rightRotation.z); + rightRotationSection.set("Angle", rightRotation.angle); + } + } + + public static void WRITE(ConfigurationSection section, + Transformation transformation) { + WRITE(section, transformation, true); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/display/RotationAxis.java b/src/main/java/us/mytheria/bloblib/entities/display/RotationAxis.java new file mode 100644 index 00000000..37afff19 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/display/RotationAxis.java @@ -0,0 +1,7 @@ +package us.mytheria.bloblib.entities.display; + +public enum RotationAxis { + X, + Y, + Z +} diff --git a/src/main/java/us/mytheria/bloblib/entities/display/TextTransformationStep.java b/src/main/java/us/mytheria/bloblib/entities/display/TextTransformationStep.java new file mode 100644 index 00000000..487fe29c --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/display/TextTransformationStep.java @@ -0,0 +1,29 @@ +package us.mytheria.bloblib.entities.display; + +import org.bukkit.Color; +import org.bukkit.util.Transformation; +import org.jetbrains.annotations.Nullable; + +public class TextTransformationStep extends TransformationStep { + private final @Nullable Color backgroundColor; + private final byte textOpacity; + + public TextTransformationStep(Transformation transformation, + float shadowRadius, + float shadowStrength, + int duration, + @Nullable Color backgroundColor, + byte textOpacity) { + super(transformation, shadowRadius, shadowStrength, duration); + this.backgroundColor = backgroundColor; + this.textOpacity = textOpacity; + } + + @Override + public TextTransformationStep copy() { + return new TextTransformationStep(new Transformation(transformation.getTranslation(), + transformation.getLeftRotation(), transformation.getScale(), transformation.getRightRotation()), + shadowRadius, shadowStrength, + duration, backgroundColor, textOpacity); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/display/TransformationStep.java b/src/main/java/us/mytheria/bloblib/entities/display/TransformationStep.java new file mode 100644 index 00000000..595323d3 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/display/TransformationStep.java @@ -0,0 +1,63 @@ +package us.mytheria.bloblib.entities.display; + +import org.bukkit.util.Transformation; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +public class TransformationStep { + protected final Transformation transformation; + protected final float shadowRadius; + protected final float shadowStrength; + protected final int duration; + + public TransformationStep(Transformation transformation, + float shadowRadius, + float shadowStrength, + int duration) { + this.transformation = transformation; + this.shadowRadius = shadowRadius; + this.shadowStrength = shadowStrength; + this.duration = duration; + } + + public static TransformationStep of(TransformationStep step, int duration) { + return new TransformationStep(step.copy().transformation, step.shadowRadius, + step.shadowStrength, duration); + } + + /** + * Will copy/clone itself to a new instance + * + * @return a new instance of TransformationStep + */ + public TransformationStep copy() { + Transformation current = transformation; + Vector3f translation = current.getTranslation(); + Quaternionf leftRotation = current.getLeftRotation(); + Vector3f scale = current.getScale(); + Quaternionf rightRotation = current.getRightRotation(); + return new TransformationStep(new Transformation( + new Vector3f(translation.x, translation.y, translation.z), + new Quaternionf(leftRotation.x, leftRotation.y, leftRotation.z, leftRotation.w), + new Vector3f(scale.x, scale.y, scale.z), + new Quaternionf(rightRotation.x, rightRotation.y, rightRotation.z, rightRotation.w)), + shadowRadius, shadowStrength, + duration); + } + + public Transformation getTransformation() { + return transformation; + } + + public float getShadowRadius() { + return shadowRadius; + } + + public float getShadowStrength() { + return shadowStrength; + } + + public int getDuration() { + return duration; + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/display/TransformationStepFactory.java b/src/main/java/us/mytheria/bloblib/entities/display/TransformationStepFactory.java new file mode 100644 index 00000000..8c074a66 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/display/TransformationStepFactory.java @@ -0,0 +1,188 @@ +package us.mytheria.bloblib.entities.display; + +import org.bukkit.util.Transformation; +import org.joml.Quaternionf; + +import java.util.List; + +public record TransformationStepFactory(TransformationStep step) { + + /** + * Will do a rotation on the left Axis + * Only supported Y axis steply + * + * @param axis the axis to rotate on + * @param degrees the degrees to rotate + * @return a new instance of TransformationBuilder + */ + public TransformationStepFactory rotateAxisLeft(RotationAxis axis, + float degrees, + int duration) { + degrees = (float) Math.toRadians(degrees); + Transformation transformation = step.copy().transformation; + Quaternionf rotation = transformation.getLeftRotation(); + switch (axis) { + case X -> { + return new TransformationStepFactory(new TransformationStep(new Transformation( + transformation.getTranslation(), + transformation.getLeftRotation().rotateX(degrees), + transformation.getScale(), + transformation.getRightRotation()), + step.shadowRadius, + step.shadowStrength, + duration)); + } + case Y -> { + return new TransformationStepFactory(new TransformationStep(new Transformation( + transformation.getTranslation(), + transformation.getLeftRotation().rotateY(degrees), + transformation.getScale(), + transformation.getRightRotation()), + step.shadowRadius, + step.shadowStrength, + duration)); + } + case Z -> { + return new TransformationStepFactory(new TransformationStep(new Transformation( + transformation.getTranslation(), + transformation.getLeftRotation().rotateZ(degrees), + transformation.getScale(), + transformation.getRightRotation()), + step.shadowRadius, + step.shadowStrength, + duration)); + } + default -> throw new UnsupportedOperationException("Not implemented yet"); + } + } + + /** + * Will do a rotation on the right Axis + * Only supported Y axis steply + * + * @param axis the axis to rotate on + * @param degrees the degrees to rotate + * @return a new instance of TransformationBuilder + */ + public TransformationStepFactory rotateAxisRight(RotationAxis axis, + float degrees, + int duration) { + degrees = (float) Math.toRadians(degrees); + Transformation transformation = step.copy().transformation; + Quaternionf rotation = transformation.getRightRotation(); + switch (axis) { + case X -> { + return new TransformationStepFactory(new TransformationStep(new Transformation( + transformation.getTranslation(), + transformation.getLeftRotation(), + transformation.getScale(), + transformation.getRightRotation().rotateX(degrees)), + step.shadowRadius, + step.shadowStrength, + duration)); + } + case Y -> { + return new TransformationStepFactory(new TransformationStep(new Transformation( + transformation.getTranslation(), + transformation.getLeftRotation(), + transformation.getScale(), + transformation.getRightRotation().rotateY(degrees)), + step.shadowRadius, + step.shadowStrength, + duration)); + } + case Z -> { + return new TransformationStepFactory(new TransformationStep(new Transformation( + transformation.getTranslation(), + transformation.getLeftRotation(), + transformation.getScale(), + transformation.getRightRotation().rotateZ(degrees)), + step.shadowRadius, + step.shadowStrength, + duration)); + } + default -> throw new UnsupportedOperationException("Not implemented yet"); + } + } + + /** + * Will uniformly scale the display entity. + * Expect/assert the same behavior as {@link #scale(float, float, float, int)} + * + * @param scale the scale to set + * @param duration the duration of the transformation (in ticks) + * @return a new instance of TransformationStep + */ + public TransformationStepFactory uniformScale(float scale, + int duration) { + TransformationStep copy = TransformationStep.of(step, duration); + copy.getTransformation().getScale().set(scale); + return new TransformationStepFactory(copy); + } + + /** + * Will scale the display entity. + * Has more freedom than {@link #uniformScale(float, int)} + * such as for doing sticks,tubes,etc. + * + * @param x the x scale + * @param y the y scale + * @param z the z scale + * @param duration the duration of the transformation (in ticks) + * @return a new instance of TransformationStep + */ + public TransformationStepFactory scale(float x, float y, float z, + int duration) { + TransformationStep copy = TransformationStep.of(step, duration); + copy.getTransformation().getScale().set(x, y, z); + return new TransformationStepFactory(copy); + } + + /** + * Will create a full rotation of 360 degrees + * facing all four possible directions + * + * @param axis the axis to rotate on + * @param duration the total duration of the transformation (in ticks) + * @return an immutable list of TransformationStep + */ + public List fullQuadRotation(RotationAxis axis, int duration) { + if (duration % 4 != 0) throw new IllegalArgumentException("Duration must be a multiple of 4"); + int tick = duration / 4; + TransformationStep a = rotateAxisLeft(axis, 90, + tick).step(); + TransformationStep b = new TransformationStepFactory(a.copy()) + .rotateAxisLeft(axis, 90, + tick).step(); + TransformationStep c = new TransformationStepFactory(b.copy()) + .rotateAxisLeft(axis, 90, + tick).step(); + TransformationStep d = new TransformationStepFactory(c.copy()) + .rotateAxisLeft(axis, 90, + tick).step(); + return List.of(a, b, c, d); + } + + /** + * Will create a full rotation of 360 degrees + * skipping a direction (instead of doing 0,90,180,270 + * it will do 0,120,240) + * + * @param axis the axis to rotate on + * @param duration the total duration of the transformation (in ticks) + * @return an immutable list of TransformationStep + */ + public List fullTriRotation(RotationAxis axis, int duration) { + if (duration % 3 != 0) throw new IllegalArgumentException("Duration must be a multiple of 3"); + int tick = duration / 3; + TransformationStep a = rotateAxisLeft(axis, 120, + tick).step(); + TransformationStep b = new TransformationStepFactory(a.copy()) + .rotateAxisLeft(axis, 120, + tick).step(); + TransformationStep c = new TransformationStepFactory(b.copy()) + .rotateAxisLeft(axis, 120, + tick).step(); + return List.of(a, b, c); + } +} \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/entities/display/TransformationStepMemory.java b/src/main/java/us/mytheria/bloblib/entities/display/TransformationStepMemory.java new file mode 100644 index 00000000..67a87f1e --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/display/TransformationStepMemory.java @@ -0,0 +1,168 @@ +package us.mytheria.bloblib.entities.display; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Display; +import org.bukkit.plugin.Plugin; + +import java.util.ArrayDeque; +import java.util.List; + +/** + * This class is used to store a list of + * transformation steps and play them + * ordered. Cycle will be repeated + * until stopped. + * + * @param the type of the display + */ +public class TransformationStepMemory { + private final Plugin plugin; + protected final ArrayDeque deque; + private boolean isRunning; + private boolean isLocked; + + /** + * Will create a new TransformationStepMemory, + * automatically adding the given list of + * transformation steps to the memory. + * The clock needs to be started manually + * + * @param list the list of transformation steps + * @param plugin the plugin to run the clock on + * @param the type of the display + * @return a new instance of TransformationStepMemory + */ + public static TransformationStepMemory of( + List list, + Plugin plugin) { + TransformationStepMemory memory = new TransformationStepMemory<>(plugin); + list.forEach(memory::addLast); + return memory; + } + + /** + * Will create a new TransformationStepMemory + * + * @param plugin the plugin to run the clock on + */ + public TransformationStepMemory(Plugin plugin) { + this.isLocked = false; + this.deque = new ArrayDeque<>(); + this.isRunning = false; + this.plugin = plugin; + } + + /** + * Will add a transformation step to the memory + * as last so the clock will later play it + * + * @param step the step to add + */ + public void addLast(TransformationStep step) { + deque.addLast(step); + } + + /** + * Will add a list of transformation steps + * to the memory as last so the clock will later + * play them + * + * @param steps the steps to add + */ + public void addLast(List steps) { + steps.forEach(this::addLast); + } + + /** + * Will play the next transformation step + * and return it + * + * @param display the display to animate + * @return the next transformation step + */ + public TransformationStep playNext(T display) { + if (!deque.isEmpty()) { + TransformationStep current = deque.removeFirst(); + deque.addLast(current.copy()); + display.setInterpolationDelay(-1); + display.setInterpolationDuration(current.getDuration()); + display.setTransformation(current.getTransformation()); + return current; + } else + throw new IllegalStateException("Memory is empty"); + } + + /** + * Will start the transformation/animation clock. + * It runs completely asynchronously. + * + * @param display the display to animate + */ + public void startClock(T display) { + if (isRunning) + return; + isRunning = true; + clock(display); + } + + /** + * Checks if the clock is running + * + * @return true if the clock is running + */ + public boolean isRunning() { + return isRunning; + } + + /** + * Checks if the clock is locked. + * Once locked, the clock can't be started again. + * Take it as a "final" state, similar to + * marking an entity for removal, + * as Entity#isValid() will return false + * + * @return true if the clock is locked + */ + public boolean isLocked() { + return isLocked; + } + + /** + * Will stop the transformation/animation clock + * + * @param step the step to stop at + * @param display the display to animate + */ + public void stopClock(TransformationStep step, T display) { + if (!isRunning) + return; + isRunning = false; + isLocked = true; + display.setInterpolationDelay(-1); + display.setInterpolationDuration(step.getDuration()); + display.setTransformation(step.getTransformation()); + } + + /** + * Will stop the transformation/animation clock + * using the next transformation step in the memory + * + * @param display the display to animate + */ + public void stopClock(T display) { + TransformationStep current = deque.peekFirst(); + if (current == null) + return; + stopClock(current, display); + } + + private void clock(T display) { + if (!isRunning) + return; + if (!display.isValid()) + return; + int duration = playNext(display).getDuration(); + Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> + clock(display), duration); + } +} \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/entities/inventory/BlobButtonManager.java b/src/main/java/us/mytheria/bloblib/entities/inventory/BlobButtonManager.java index 1a0b4e21..3b37f68c 100644 --- a/src/main/java/us/mytheria/bloblib/entities/inventory/BlobButtonManager.java +++ b/src/main/java/us/mytheria/bloblib/entities/inventory/BlobButtonManager.java @@ -3,7 +3,6 @@ import me.anjoismysign.anjo.entities.Uber; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.inventory.ItemStack; -import us.mytheria.bloblib.entities.BlobMultiSlotable; import java.util.Collection; import java.util.HashMap; @@ -20,7 +19,7 @@ *

* It handles the above through HashMaps giving it a O(1) time complexity. */ -public class BlobButtonManager extends ButtonManager { +public class BlobButtonManager extends ButtonManager { /** * Builds a ButtonManager through the specified ConfigurationSection. * Uses HashMap to store buttons. @@ -34,28 +33,21 @@ public static BlobButtonManager fromConfigurationSection(ConfigurationSection se return blobButtonManager; } - /** - * Builds a ButtonManager through the specified ConfigurationSection. - * Uses HashMap to store buttons. - * - * @param section configuration section which contains all the buttons - * @return a non abstract ButtonManager. - * @deprecated Smart methods were made during development and are already - * safe to use. Use {@link #fromConfigurationSection(ConfigurationSection)} instead - * which is identical to this method. - */ - @Deprecated - public static BlobButtonManager smartFromConfigurationSection(ConfigurationSection section) { - BlobButtonManager blobButtonManager = new BlobButtonManager(); - blobButtonManager.read(section); - return blobButtonManager; - } - /** * Builds a non abstract ButtonManager without any buttons stored yet. */ public BlobButtonManager() { - super(new HashMap<>(), new HashMap<>()); + this(new ButtonManagerData<>(new HashMap<>(), new HashMap<>())); + } + + private BlobButtonManager(ButtonManagerData buttonManagerData) { + super(buttonManagerData); + } + + @Override + public BlobButtonManager copy() { + return new BlobButtonManager( + new ButtonManagerData<>(copyStringKeys(), copyIntegerKeys())); } /** @@ -83,12 +75,15 @@ public boolean contains(int slot) { /** * Gets all buttons stored in this ButtonManager that belong to the specified key * - * @param key the key of the buttons - * @return all buttons stored in this ButtonManager that belong to the specified key + * @param key the key of the InventoryButton + * @return all buttons stored in this ButtonManager that belong to the specified key. + * null if the key is not stored in this ButtonManager */ @Override public Set get(String key) { - return getStringKeys().get(key); + if (contains(key)) + return getStringKeys().get(key).getSlots(); + return null; } /** @@ -140,27 +135,4 @@ public boolean add(ConfigurationSection section) { }); return madeChanges.thanks(); } - - /** - * adds all buttons inside a configuration section through parsing - * - * @param section configuration section which contains all the buttons - * @return true if at least one button was succesfully added. - * this is determined in case the being called after the first add call - * @deprecated 'read' method was made during development but is ready and - * safe to use. Use {@link #add(ConfigurationSection)} instead which - * is identical to this method. - */ - @Deprecated - @Override - public boolean read(ConfigurationSection section) { - Set set = section.getKeys(false); - Uber madeChanges = new Uber<>(false); - set.stream().filter(key -> !contains(key)).forEach(key -> { - madeChanges.talk(true); - BlobMultiSlotable slotable = BlobMultiSlotable.read(section.getConfigurationSection(key), key); - slotable.setInButtonManager(this); - }); - return madeChanges.thanks(); - } } diff --git a/src/main/java/us/mytheria/bloblib/entities/inventory/BlobInventory.java b/src/main/java/us/mytheria/bloblib/entities/inventory/BlobInventory.java index 58dae165..691fa04e 100644 --- a/src/main/java/us/mytheria/bloblib/entities/inventory/BlobInventory.java +++ b/src/main/java/us/mytheria/bloblib/entities/inventory/BlobInventory.java @@ -1,353 +1,22 @@ package us.mytheria.bloblib.entities.inventory; -import org.bukkit.Bukkit; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import us.mytheria.bloblib.utilities.TextColor; +import org.jetbrains.annotations.NotNull; -import java.io.File; -import java.util.HashMap; -import java.util.Objects; +public class BlobInventory extends SharableInventory { -/** - * @author anjoismysign - *

- * A BlobInventory is an instance of a InventoryBuilder. - * It's my own version/way for managing GUI's / inventories - * using the Bukkit API. - */ -public class BlobInventory extends InventoryBuilder { - private Inventory inventory; - private HashMap defaultButtons; - - /** - * A way to create a BlobInventory from a file. - * This way is a bit more efficient in some IDE's since after - * typing BlobInventory and a dot, it will show you the - * static methods, such as 'fromFile' and will also prompt - * the file parameter you should already have. - *

-     *     BlobInventory inventory = new BlobInventory(file);
-     *
-     *     assert inventory.equals(BlobInventory.fromFile(file));
-     *     
- * - * @param file The file to load the inventory from. - * @return The BlobInventory loaded from the file. - * @deprecated Smart methods were made during development and are already - * safe to use. Use {@link #fromFile(File)} instead which is - * identical to this method. - */ - @Deprecated - public static BlobInventory smartFromFile(File file) { - YamlConfiguration configuration = YamlConfiguration.loadConfiguration( - Objects.requireNonNull(file, "'file' cannot be null!")); - String title = TextColor.PARSE(configuration.getString("Title", configuration.getName() + ">NOT-SET")); - int size = configuration.getInt("Size", -1); - if (size < 0 || size % 9 != 0) { - if (size < 0) { - size = 54; - Bukkit.getLogger().info(configuration.getName() + "'s Size is smaller than 0."); - Bukkit.getLogger().info("This was probably due because you never set a Size."); - Bukkit.getLogger().info("This is not possible in an inventory so it was set"); - Bukkit.getLogger().info("to '54' which is default."); - } else { - size = 54; - Bukkit.getLogger().info(configuration.getName() + "'s Size is not a factor of 9."); - Bukkit.getLogger().info("This is not possible in an inventory so it was set"); - Bukkit.getLogger().info("to '54' which is default."); - } - } - BlobButtonManager buttonManager = BlobButtonManager.smartFromConfigurationSection(configuration.getConfigurationSection("Buttons")); - return new BlobInventory(title, size, buttonManager); - } - - /** - * A way to create a BlobInventory from a file. - * You need to be sure that provided file only - * contains a BlobInventory since in InventoryManager - * there's a method that can load multiple inventories - * inside a single File. - * This way is a bit more efficient in some IDE's since after - * typing BlobInventory and a dot, it will show you the - * static methods, such as 'fromFile' and will also prompt - * the file parameter you should already have. - *
-     *     BlobInventory inventory = new BlobInventory(file);
-     *
-     *     assert inventory.equals(BlobInventory.fromFile(file));
-     *     
- * - * @param file The file to load the inventory from. - * @return The BlobInventory loaded from the file. - */ - public static BlobInventory fromFile(File file) { - YamlConfiguration configuration = YamlConfiguration.loadConfiguration( - Objects.requireNonNull(file, "'file' cannot be null!")); - String title = TextColor.PARSE(configuration.getString("Title", configuration.getName() + ">NOT-SET")); - int size = configuration.getInt("Size", -1); - if (size < 0 || size % 9 != 0) { - if (size < 0) { - size = 54; - Bukkit.getLogger().info(configuration.getName() + "'s Size is smaller than 0."); - Bukkit.getLogger().info("This was probably due because you never set a Size."); - Bukkit.getLogger().info("This is not possible in an inventory so it was set"); - Bukkit.getLogger().info("to '54' which is default."); - } else { - size = 54; - Bukkit.getLogger().info(configuration.getName() + "'s Size is not a factor of 9."); - Bukkit.getLogger().info("This is not possible in an inventory so it was set"); - Bukkit.getLogger().info("to '54' which is default."); - } - } - BlobButtonManager buttonManager = BlobButtonManager.fromConfigurationSection(configuration.getConfigurationSection("Buttons")); - return new BlobInventory(title, size, buttonManager); - } - - /** - * Parses a BlobInventory from a ConfigurationSection. - * - * @param configurationSection The ConfigurationSection to parse from. - * @return The BlobInventory parsed from the ConfigurationSection. - * @deprecated Smart methods were made during development and are already - * safe to use. Use {@link #fromConfigurationSection(ConfigurationSection)} instead - * which is identical to this method. - */ - @Deprecated - public static BlobInventory smartFromConfigurationSection(ConfigurationSection configurationSection) { - String title = TextColor.PARSE(Objects.requireNonNull(configurationSection, - "'configurationSection' cannot be null!").getString("Title", configurationSection.getName() + ">NOT-SET")); - int size = configurationSection.getInt("Size", -1); - if (size < 0 || size % 9 != 0) { - if (size < 0) { - size = 54; - Bukkit.getLogger().info(configurationSection.getName() + "'s Size is smaller than 0."); - Bukkit.getLogger().info("This was probably due because you never set a Size."); - Bukkit.getLogger().info("This is not possible in an inventory so it was set"); - Bukkit.getLogger().info("to '54' which is default."); - } else { - size = 54; - Bukkit.getLogger().info(configurationSection.getName() + "'s Size is not a factor of 9."); - Bukkit.getLogger().info("This is not possible in an inventory so it was set"); - Bukkit.getLogger().info("to '54' which is default."); - } - } - BlobButtonManager buttonManager = BlobButtonManager.smartFromConfigurationSection(configurationSection.getConfigurationSection("Buttons")); - return new BlobInventory(title, size, buttonManager); - } - - /** - * Parses a BlobInventory from a ConfigurationSection. - * - * @param configurationSection The ConfigurationSection to parse from. - * @return The BlobInventory parsed from the ConfigurationSection. - */ - public static BlobInventory fromConfigurationSection(ConfigurationSection configurationSection) { - String title = TextColor.PARSE(Objects.requireNonNull(configurationSection, - "'configurationSection' cannot be null!").getString("Title", configurationSection.getName() + ">NOT-SET")); - int size = configurationSection.getInt("Size", -1); - if (size < 0 || size % 9 != 0) { - if (size < 0) { - size = 54; - Bukkit.getLogger().info(configurationSection.getName() + "'s Size is smaller than 0."); - Bukkit.getLogger().info("This was probably due because you never set a Size."); - Bukkit.getLogger().info("This is not possible in an inventory so it was set"); - Bukkit.getLogger().info("to '54' which is default."); - } else { - size = 54; - Bukkit.getLogger().info(configurationSection.getName() + "'s Size is not a factor of 9."); - Bukkit.getLogger().info("This is not possible in an inventory so it was set"); - Bukkit.getLogger().info("to '54' which is default."); - } - } - BlobButtonManager buttonManager = BlobButtonManager.fromConfigurationSection(configurationSection.getConfigurationSection("Buttons")); - return new BlobInventory(title, size, buttonManager); - } - - /** - * Constructs a BlobInventory with a title, size, and a ButtonManager. - * - * @param title The title of the inventory. - * @param size The size of the inventory. - * @param buttonManager The ButtonManager that will be used to manage the buttons. - */ - protected BlobInventory(String title, int size, ButtonManager buttonManager) { - this.setTitle(Objects.requireNonNull(title, - "'title' cannot be null!")); - this.setSize(size); - this.setButtonManager(Objects.requireNonNull(buttonManager, - "'buttonManager' cannot be null!")); - this.buildInventory(); - this.loadDefaultButtons(); + public static BlobInventory fromInventoryBuilderCarrier(InventoryBuilderCarrier carrier) { + return new BlobInventory(carrier.title(), carrier.size(), + carrier.buttonManager().copy()); } - /** - * Parses a BlobInventory from a File. - * - * @param file The File to parse from. - */ - public BlobInventory(File file) { - YamlConfiguration configuration = YamlConfiguration.loadConfiguration(file); - setTitle(TextColor.PARSE(configuration.getString("Title", configuration.getName() + ">NOT-SET"))); - setSize(configuration.getInt("Size", -1)); - int size = getSize(); - if (size < 0 || size % 9 != 0) { - setSize(54); - if (size < 0) { - Bukkit.getLogger().info(configuration.getName() + "'s Size is smaller than 0."); - Bukkit.getLogger().info("This was probably due because you never set a Size."); - } else { - Bukkit.getLogger().info(configuration.getName() + "'s Size is not a factor of 9."); - } - Bukkit.getLogger().info("This is not possible in an inventory so it was set"); - Bukkit.getLogger().info("to '54' which is default."); - } - setButtonManager(BlobButtonManager - .fromConfigurationSection(configuration - .getConfigurationSection("Buttons"))); - this.buildInventory(); - this.loadDefaultButtons(); + public BlobInventory(@NotNull String title, int size, + @NotNull ButtonManager buttonManager) { + super(title, size, buttonManager); } - /** - * Creates a new BlobInventory in an empty constructor. - *

- * I recommend you try constructing your BlobInventory - * and get used to the API for a strong workflow. - */ - public BlobInventory() { - } - - /** - * Copies itself to a new reference. - * - * @return The cloned inventory - */ + @Override + @NotNull public BlobInventory copy() { - return new BlobInventory(getTitle(), getSize(), getButtonManager()); - } - - /** - * Adds a default button using the specified key. - * Default buttons are meant to persist inventory - * changes. - * - * @param key The key of the button - */ - public void addDefaultButton(String key) { - for (Integer i : getSlots(key)) { - addDefaultButton(key, getButton(i)); - break; - } - } - - /** - * Loads/reloads the default buttons. - */ - public void loadDefaultButtons() { - setDefaultButtons(new HashMap<>()); - getKeys().forEach(this::addDefaultButton); - } - - /** - * Adds a default button to the memory of the inventory. - * - * @param name The name of the button - * @param item The ItemStack of the button - */ - public void addDefaultButton(String name, ItemStack item) { - defaultButtons.put(name, item); - } - - /** - * Searches for the ItemStack that is linked to said key. - * - * @param key The key of the button - * @return The ItemStack - */ - public ItemStack getDefaultButton(String key) { - return defaultButtons.get(key); - } - - /** - * Searches for the ItemStack that is linked to said key. - * The ItemStack is cloned and then returned. - * - * @param key The key of the button - * @return The cloned ItemStack - */ - public ItemStack cloneDefaultButton(String key) { - return defaultButtons.get(key).clone(); - } - - /** - * Retrieves the default buttons - * - * @return The default buttons - */ - public HashMap getDefaultButtons() { - return defaultButtons; - } - - /** - * Sets the default buttons - * - * @param defaultButtons The default buttons to set - */ - public void setDefaultButtons(HashMap defaultButtons) { - this.defaultButtons = defaultButtons; - } - - /** - * Builds the inventory since by default its reference is null - */ - public void buildInventory() { - inventory = Bukkit.createInventory(null, getSize(), getTitle()); - getButtonManager().getIntegerKeys().forEach((integer, itemStack) -> { - if (integer >= getSize()) - throw new IllegalArgumentException("The slot '" + integer + "' is larger than the size of the inventory."); - inventory.setItem(integer, itemStack); - }); - } - - /** - * Retrieves the inventory - * - * @return The inventory - */ - public Inventory getInventory() { - return inventory; - } - - /** - * Sets the inventory - * - * @param inventory The inventory to set - */ - public void setInventory(Inventory inventory) { - this.inventory = inventory; - } - - /** - * Sets the button at the specified slot - * - * @param slot The slot to set the button at - * @param itemStack The ItemStack to set the button to - */ - public void setButton(int slot, ItemStack itemStack) { - inventory.setItem(slot, itemStack); - } - - /** - * Refills all ItemStacks that belong to said key - * - * @param key The key of the button - */ - public void refillButton(String key) { - for (Integer i : getSlots(key)) { - setButton(i, getButton(i)); - } + return new BlobInventory(getTitle(), getSize(), getButtonManager().copy()); } } diff --git a/src/main/java/us/mytheria/bloblib/entities/inventory/BlobInventoryHolder.java b/src/main/java/us/mytheria/bloblib/entities/inventory/BlobInventoryHolder.java new file mode 100644 index 00000000..6ea21752 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/inventory/BlobInventoryHolder.java @@ -0,0 +1,31 @@ +package us.mytheria.bloblib.entities.inventory; + +import org.bukkit.inventory.InventoryHolder; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class BlobInventoryHolder extends InventoryHolderBuilder { + + public static BlobInventoryHolder fromInventoryBuilderCarrier(InventoryBuilderCarrier carrier, + @Nullable InventoryHolder holder) { + return new BlobInventoryHolder(carrier.title(), carrier.size(), carrier.buttonManager(), holder); + } + + public BlobInventoryHolder(@NotNull String title, int size, + @NotNull ButtonManager buttonManager, + @Nullable InventoryHolder holder) { + super(title, size, buttonManager, holder); + } + + @Override + @NotNull + public BlobInventoryHolder copy() { + return new BlobInventoryHolder(getTitle(), getSize(), getButtonManager(), getHolder()); + } + + @Override + @NotNull + public BlobInventoryHolder setHolder(@NotNull InventoryHolder holder) { + return new BlobInventoryHolder(getTitle(), getSize(), getButtonManager(), holder); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/BlobMultiSlotable.java b/src/main/java/us/mytheria/bloblib/entities/inventory/BlobMultiSlotable.java similarity index 74% rename from src/main/java/us/mytheria/bloblib/entities/BlobMultiSlotable.java rename to src/main/java/us/mytheria/bloblib/entities/inventory/BlobMultiSlotable.java index 84b74d3a..70f2866e 100644 --- a/src/main/java/us/mytheria/bloblib/entities/BlobMultiSlotable.java +++ b/src/main/java/us/mytheria/bloblib/entities/inventory/BlobMultiSlotable.java @@ -1,12 +1,11 @@ -package us.mytheria.bloblib.entities; +package us.mytheria.bloblib.entities.inventory; import org.bukkit.Bukkit; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; -import us.mytheria.bloblib.entities.inventory.ButtonManager; +import org.jetbrains.annotations.Nullable; import us.mytheria.bloblib.itemstack.ItemStackReader; -import us.mytheria.bloblib.objects.SerializableItem; import java.util.HashSet; import java.util.Set; @@ -27,32 +26,6 @@ public class BlobMultiSlotable extends MultiSlotable { private final String key; - /** - * Parses/reads from a ConfigurationSection using SerializableItem. - * - * @param section The ConfigurationSection to read from. - * @param key The key of the BlobMultiSlotable which was intended to read from. - * @return The BlobMultiSlotable which was read from the ConfigurationSection. - * @deprecated Use {@link #read(ConfigurationSection, String)} instead - * since SerializableItem is deprecated due to ItemStackReader being - * the new way to read ItemStacks from ConfigurationSections. - */ - @Deprecated - public static BlobMultiSlotable fromConfigurationSection(ConfigurationSection section, String key) { - ItemStack itemStack = SerializableItem.fromConfigurationSection(section.getConfigurationSection("ItemStack")); - HashSet list = new HashSet<>(); - String read = section.getString("Slot", "-1"); - String[] slots = read.split(","); - if (slots.length != 1) { - for (String slot : slots) { - add(list, slot, section.getName()); - } - } else { - add(list, read, section.getName()); - } - return new BlobMultiSlotable(list, itemStack, key); - } - /** * Parses/reads from a ConfigurationSection using ItemStackReader. * @@ -66,7 +39,7 @@ public static BlobMultiSlotable read(ConfigurationSection section, String key) { Bukkit.getLogger().severe("ItemStack section is null for " + key); return null; } - ItemStack itemStack = ItemStackReader.read(itemStackSection).build(); + ItemStack itemStack = ItemStackReader.READ_OR_FAIL_FAST(itemStackSection).build(); HashSet list = new HashSet<>(); String read = section.getString("Slot", "-1"); String[] slots = read.split(","); @@ -77,18 +50,41 @@ public static BlobMultiSlotable read(ConfigurationSection section, String key) { } else { add(list, read, section.getName()); } - return new BlobMultiSlotable(list, itemStack, key); + String permission = null; + if (section.isString("Permission")) + permission = section.getString("Permission"); + double price = 0; + if (section.isDouble("Price")) { + price = section.getDouble("Price"); + } + String priceCurrency = null; + if (section.isString("Price-Currency")) + priceCurrency = section.getString("Price-Currency"); + String action = null; + if (section.isString("Action")) + action = section.getString("Action"); + return new BlobMultiSlotable(list, itemStack, key, permission, price, + priceCurrency, action); } /** * Constructor for BlobMultiSlotable * - * @param slots The slots to add the item to - * @param itemStack The item to add - * @param key The key to use for the item + * @param slots The slots to add the item to + * @param itemStack The item to add + * @param key The key to use for the item + * @param permission The permission to use for the item + * @param price The price to use for the item + * @param priceCurrency The price currency to use for the item + * @param action The action to use for the item */ - public BlobMultiSlotable(Set slots, ItemStack itemStack, String key) { - super(slots, itemStack); + public BlobMultiSlotable(Set slots, ItemStack itemStack, + String key, + @Nullable String permission, + double price, + @Nullable String priceCurrency, + @Nullable String action) { + super(slots, itemStack, permission, price, priceCurrency, action); this.key = key; } @@ -105,6 +101,11 @@ public void setInInventory(Inventory inventory) { } } + public InventoryButton toInventoryButton() { + return new InventoryButton(key, getSlots(), getPermission(), getPrice(), + getPriceCurrency(), getAction()); + } + /** * Writes in a ButtonManager (in case of working * with BlobLib's GUI system) the BlobMultiSlotable @@ -112,8 +113,8 @@ public void setInInventory(Inventory inventory) { * @param buttonManager The ButtonManager to write in * the BlobMultiSlotable */ - public void setInButtonManager(ButtonManager buttonManager) { - buttonManager.getStringKeys().put(key, this.getSlots()); + public void setInButtonManager(ButtonManager buttonManager) { + buttonManager.getStringKeys().put(key, toInventoryButton()); for (Integer slot : getSlots()) { buttonManager.getIntegerKeys().put(slot, getItemStack()); } diff --git a/src/main/java/us/mytheria/bloblib/entities/inventory/BlobPlayerInventoryHolder.java b/src/main/java/us/mytheria/bloblib/entities/inventory/BlobPlayerInventoryHolder.java new file mode 100644 index 00000000..b3bb59f8 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/inventory/BlobPlayerInventoryHolder.java @@ -0,0 +1,31 @@ +package us.mytheria.bloblib.entities.inventory; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +public class BlobPlayerInventoryHolder extends PlayerInventoryBuilder { + + public static BlobPlayerInventoryHolder fromInventoryBuilderCarrier(InventoryBuilderCarrier carrier, @Nullable UUID holderId) { + return new BlobPlayerInventoryHolder(carrier.title(), carrier.size(), carrier.buttonManager(), holderId); + } + + public BlobPlayerInventoryHolder(@NotNull String title, int size, + @NotNull ButtonManager buttonManager, + @Nullable UUID holderId) { + super(title, size, buttonManager, holderId); + } + + @Override + @NotNull + public BlobPlayerInventoryHolder copy() { + return new BlobPlayerInventoryHolder(getTitle(), getSize(), getButtonManager(), getHolderId()); + } + + @Override + @NotNull + public BlobPlayerInventoryHolder setHolderId(@NotNull UUID holderId) { + return new BlobPlayerInventoryHolder(getTitle(), getSize(), getButtonManager(), holderId); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/inventory/ButtonManager.java b/src/main/java/us/mytheria/bloblib/entities/inventory/ButtonManager.java index 481f2be5..3be01ac1 100644 --- a/src/main/java/us/mytheria/bloblib/entities/inventory/ButtonManager.java +++ b/src/main/java/us/mytheria/bloblib/entities/inventory/ButtonManager.java @@ -1,20 +1,21 @@ package us.mytheria.bloblib.entities.inventory; import org.bukkit.inventory.ItemStack; -import us.mytheria.bloblib.entities.InventoryButton; -import us.mytheria.bloblib.entities.SuperInventoryButton; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.util.Collection; +import java.util.HashMap; import java.util.Map; import java.util.Set; -public abstract class ButtonManager implements ButtonManagerMethods { - private Map> stringKeys; +public abstract class ButtonManager implements ButtonManagerMethods { + private Map stringKeys; private Map integerKeys; - public ButtonManager(Map> stringKeys, - Map integerKeys) { - this.stringKeys = stringKeys; - this.integerKeys = integerKeys; + public ButtonManager(ButtonManagerData buttonManagerData) { + this.stringKeys = new HashMap<>(buttonManagerData.inventoryButtons()); + this.integerKeys = new HashMap<>(buttonManagerData.itemStackMap()); } public ButtonManager() { @@ -24,7 +25,7 @@ public Map getIntegerKeys() { return integerKeys; } - public Map> getStringKeys() { + public Map getStringKeys() { return stringKeys; } @@ -32,7 +33,7 @@ public void setIntegerKeys(Map integerKeys) { this.integerKeys = integerKeys; } - public void setStringKeys(Map> stringKeys) { + public void setStringKeys(Map stringKeys) { this.stringKeys = stringKeys; } @@ -40,19 +41,56 @@ public Set getSlots(String key) { return get(key); } - public InventoryButton getButton(String key) { - return new InventoryButton(key, getSlots(key)); + /** + * Checks if the InventoryButton for the specified key is stored in this ButtonManager + * + * @param key the key of the InventoryButton + * @return the InventoryButton for the specified key is stored in this ButtonManager + * null if the InventoryButton for the specified key is not stored in this ButtonManager + */ + @Override + @Nullable + public T getButton(String key) { + return getStringKeys().get(key); } - - public void addCommand(String key, String command) { + /** + * @return all InventoryButtons stored in this ButtonManager + */ + @NotNull + public Collection getAllButtons() { + return getStringKeys().values(); } - public String getCommand(String key) { - return null; + /** + * Will copy/clone the ButtonManager to a new instance + * + * @return the new instance of the ButtonManager + */ + public abstract ButtonManager copy(); + + /** + * Copies stringKeys map used in ButtonManager constructor + * + * @return a copy of the stringKeys map + */ + @SuppressWarnings("unchecked") + public Map copyStringKeys() { + Map stringKeys = new HashMap<>(); + this.stringKeys.forEach((key, value) -> { + stringKeys.put(key, (T) value.copy()); + }); + return stringKeys; } - public SuperInventoryButton getSuperButton(String key) { - return null; + /** + * Copies integerKeys map used in ButtonManager constructor + * + * @return a copy of the integerKeys map + */ + public Map copyIntegerKeys() { + Map map = new HashMap<>(); + this.integerKeys.forEach((key, value) -> map.put(key, new ItemStack(value))); + return map; } } diff --git a/src/main/java/us/mytheria/bloblib/entities/inventory/ButtonManagerData.java b/src/main/java/us/mytheria/bloblib/entities/inventory/ButtonManagerData.java new file mode 100644 index 00000000..0bea3e53 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/inventory/ButtonManagerData.java @@ -0,0 +1,15 @@ +package us.mytheria.bloblib.entities.inventory; + +import org.bukkit.inventory.ItemStack; + +import java.util.Collections; +import java.util.Map; + +public record ButtonManagerData(Map inventoryButtons, + Map itemStackMap) { + public static ButtonManagerData of(Map inventoryButtons, + Map itemStackMap) { + return new ButtonManagerData<>(Collections.unmodifiableMap(inventoryButtons), + Collections.unmodifiableMap(itemStackMap)); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/inventory/ButtonManagerMethods.java b/src/main/java/us/mytheria/bloblib/entities/inventory/ButtonManagerMethods.java index 382aae73..a1b372ed 100644 --- a/src/main/java/us/mytheria/bloblib/entities/inventory/ButtonManagerMethods.java +++ b/src/main/java/us/mytheria/bloblib/entities/inventory/ButtonManagerMethods.java @@ -8,8 +8,8 @@ public interface ButtonManagerMethods { /** - * @param key the key of the ItemStack - * @return true if the specified key is stored in this ButtonManager + * @param key the key of the InventoryButton + * @return true if the specified key points to a stored InventoryButton in this ButtonManager */ boolean contains(String key); @@ -20,7 +20,7 @@ public interface ButtonManagerMethods { boolean contains(int slot); /** - * @param key the key of the ItemStack + * @param key the key of the InventoryButton * @return the slots in which the ItemStack is stored */ Set get(String key); @@ -45,16 +45,13 @@ public interface ButtonManagerMethods { boolean add(ConfigurationSection section); /** - * adds all buttons inside a configuration section through parsing - * - * @param section the configuration section to read from - * @return true if all buttons were added successfully + * @return a set of all the keys stored in this ButtonManager */ - @Deprecated - boolean read(ConfigurationSection section); + Collection keys(); /** - * @return a set of all the keys stored in this ButtonManager + * @param key the key of the InventoryButton + * @return the InventoryButton stored in this ButtonManager */ - Collection keys(); + InventoryButton getButton(String key); } diff --git a/src/main/java/us/mytheria/bloblib/entities/inventory/InventoryBuilder.java b/src/main/java/us/mytheria/bloblib/entities/inventory/InventoryBuilder.java index 990b53ed..2c2fccd3 100644 --- a/src/main/java/us/mytheria/bloblib/entities/inventory/InventoryBuilder.java +++ b/src/main/java/us/mytheria/bloblib/entities/inventory/InventoryBuilder.java @@ -1,15 +1,17 @@ package us.mytheria.bloblib.entities.inventory; +import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Nullable; +import us.mytheria.bloblib.BlobLib; import java.util.Collection; import java.util.Set; -public abstract class InventoryBuilder { +public abstract class InventoryBuilder { private String title; private int size; - private ButtonManager buttonManager; + private ButtonManager buttonManager; public String getTitle() { return title; @@ -27,11 +29,11 @@ public void setSize(int size) { this.size = size; } - public ButtonManager getButtonManager() { + public ButtonManager getButtonManager() { return buttonManager; } - public void setButtonManager(ButtonManager buttonManager) { + public void setButtonManager(ButtonManager buttonManager) { this.buttonManager = buttonManager; } @@ -44,7 +46,39 @@ public ItemStack getButton(int slot) { return buttonManager.get(slot); } + public T getButton(String key) { + return buttonManager.getButton(key); + } + public Collection getKeys() { return buttonManager.keys(); } + + public boolean isInsideButton(String key, int slot) { + T button = getButton(key); + if (button == null) { + BlobLib.getAnjoLogger().singleError("InventoryButton with key '" + key + "' inside " + + "inventory '" + getTitle() + "' does not exist!"); + return false; + } + return button.containsSlot(slot); + } + + /** + * Will handle both the permission and payment of the button. + * Should always be checked! + * + * @param key The key of the button. + * @param player The player to handle the permission and payment for. + * @return Whether the permission and payment was handled successfully. + */ + public boolean handleAll(String key, Player player) { + T button = getButton(key); + if (button == null) { + BlobLib.getAnjoLogger().singleError("InventoryButton with key '" + key + "' inside " + + "inventory '" + getTitle() + "' does not exist!"); + return false; + } + return button.handleAll(player); + } } diff --git a/src/main/java/us/mytheria/bloblib/entities/inventory/InventoryBuilderCarrier.java b/src/main/java/us/mytheria/bloblib/entities/inventory/InventoryBuilderCarrier.java new file mode 100644 index 00000000..fdffa559 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/inventory/InventoryBuilderCarrier.java @@ -0,0 +1,84 @@ +package us.mytheria.bloblib.entities.inventory; + +import org.apache.commons.io.FilenameUtils; +import org.bukkit.Bukkit; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import us.mytheria.bloblib.utilities.TextColor; + +import java.io.File; +import java.util.Objects; + +public record InventoryBuilderCarrier(@Nullable String title, + @NotNull int size, + @NotNull ButtonManager buttonManager, + @Nullable String type, + @NotNull String reference) { + public boolean isMetaInventoryButton() { + return type != null; + } + + public static InventoryBuilderCarrier BLOB_FROM_FILE(@NotNull File file) { + YamlConfiguration configuration = YamlConfiguration.loadConfiguration( + Objects.requireNonNull(file, "'file' cannot be null!")); + String fileName = FilenameUtils.removeExtension(file.getName()); + return BLOB_FROM_CONFIGURATION_SECTION(configuration, fileName); + } + + public static InventoryBuilderCarrier BLOB_FROM_CONFIGURATION_SECTION( + @NotNull ConfigurationSection configurationSection, @NotNull String reference) { + String title = TextColor.PARSE(Objects.requireNonNull(configurationSection, + "'configurationSection' cannot be null!").getString("Title", configurationSection.getName() + ">NOT-SET")); + int size = configurationSection.getInt("Size", -1); + if (size < 0 || size % 9 != 0) { + if (size < 0) { + size = 54; + Bukkit.getLogger().info(configurationSection.getName() + "'s Size is smaller than 0."); + Bukkit.getLogger().info("This was probably due because you never set a Size."); + Bukkit.getLogger().info("This is not possible in an inventory so it was set"); + Bukkit.getLogger().info("to '54' which is default."); + } else { + size = 54; + Bukkit.getLogger().info(configurationSection.getName() + "'s Size is not a factor of 9."); + Bukkit.getLogger().info("This is not possible in an inventory so it was set"); + Bukkit.getLogger().info("to '54' which is default."); + } + } + BlobButtonManager buttonManager = BlobButtonManager.fromConfigurationSection(configurationSection.getConfigurationSection("Buttons")); + return new InventoryBuilderCarrier<>(title, size, buttonManager, null, reference); + } + + public static InventoryBuilderCarrier META_FROM_FILE(@NotNull File file) { + YamlConfiguration configuration = YamlConfiguration.loadConfiguration( + Objects.requireNonNull(file, "'file' cannot be null!")); + String fileName = FilenameUtils.removeExtension(file.getName()); + return META_FROM_CONFIGURATION_SECTION(configuration, fileName); + } + + public static InventoryBuilderCarrier META_FROM_CONFIGURATION_SECTION( + @NotNull ConfigurationSection configurationSection, @NotNull String reference) { + String title = TextColor.PARSE(Objects.requireNonNull(configurationSection, + "'configurationSection' cannot be null!").getString("Title", configurationSection.getName() + ">NOT-SET")); + int size = configurationSection.getInt("Size", -1); + if (size < 0 || size % 9 != 0) { + if (size < 0) { + size = 54; + Bukkit.getLogger().info(configurationSection.getName() + "'s Size is smaller than 0."); + Bukkit.getLogger().info("This was probably due because you never set a Size."); + Bukkit.getLogger().info("This is not possible in an inventory so it was set"); + Bukkit.getLogger().info("to '54' which is default."); + } else { + size = 54; + Bukkit.getLogger().info(configurationSection.getName() + "'s Size is not a factor of 9."); + Bukkit.getLogger().info("This is not possible in an inventory so it was set"); + Bukkit.getLogger().info("to '54' which is default."); + } + } + String type = configurationSection.isString("Type") + ? configurationSection.getString("Type") : "DEFAULT"; + MetaBlobButtonManager buttonManager = MetaBlobButtonManager.fromConfigurationSection(configurationSection.getConfigurationSection("Buttons")); + return new InventoryBuilderCarrier<>(title, size, buttonManager, type, reference); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/inventory/InventoryButton.java b/src/main/java/us/mytheria/bloblib/entities/inventory/InventoryButton.java new file mode 100644 index 00000000..828accba --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/inventory/InventoryButton.java @@ -0,0 +1,166 @@ +package us.mytheria.bloblib.entities.inventory; + +import net.milkbowl.vault.economy.IdentityEconomy; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.permissions.Permissible; +import org.jetbrains.annotations.Nullable; +import us.mytheria.bloblib.action.Action; +import us.mytheria.bloblib.api.BlobLibActionAPI; +import us.mytheria.bloblib.api.BlobLibEconomyAPI; +import us.mytheria.bloblib.vault.multieconomy.ElasticEconomy; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +public class InventoryButton { + private final String key; + private final Set slots; + private final String permission; + private final double price; + private final String priceCurrency; + private final String action; + + public InventoryButton(String key, Set slots, + @Nullable String permission, + double price, @Nullable String priceCurrency, + @Nullable String action) { + this.key = key; + this.slots = slots; + this.permission = permission; + this.price = price; + this.priceCurrency = priceCurrency; + this.action = action; + } + + public String getKey() { + return key; + } + + public Set getSlots() { + return slots; + } + + @Nullable + public String getPermission() { + return permission; + } + + public boolean requiresPermission() { + return permission != null; + } + + public double getPrice() { + return price; + } + + @Nullable + public String getPriceCurrency() { + return priceCurrency; + } + + public boolean priceUseCustomCurrency() { + return priceCurrency != null; + } + + public void setDisplay(ItemStack display, ButtonManager manager) { + slots.forEach(slot -> manager.getIntegerKeys().put(slot, display)); + } + + public void setDisplay(ItemStack display, SharableInventory inventory) { + slots.forEach(slot -> { + inventory.getButtonManager().getIntegerKeys().put(slot, display); + inventory.setButton(slot, display); + }); + } + + public boolean containsSlot(int slot) { + return slots.contains(slot); + } + + /** + * Will handle the permission of the button. + * If permissible has permission, it will return true. + * If permissible does not have permission, it will return false. + * + * @param permissible The permissible to handle the permission for. + * @return Whether the permission was handled successfully. + */ + public boolean handlePermission(Permissible permissible) { + if (!requiresPermission()) + return true; + return permissible.hasPermission(getPermission()); + } + + /** + * Will handle the payment of the button. + * If the price is 1E-12 or less, it will return true. + * If the player does not have enough money, it will return false. + * If the player has enough money, it will withdraw the money and return true. + * Currently, doesn't support custom currencies since the API for it + * is still under development. + * + * @param player The player to handle the payment for. + * @return Whether the payment was handled successfully. + */ + public boolean handlePayment(Player player) { + double price = getPrice(); + if (Double.compare(price, 0D) <= 0) + return true; + ElasticEconomy elasticEconomy = BlobLibEconomyAPI.getInstance().getElasticEconomy(); + IdentityEconomy economy = elasticEconomy + .map(Optional.ofNullable(getPriceCurrency())); + boolean hasAmount = economy.has(player.getUniqueId(), price); + if (!hasAmount) + return false; + economy.withdraw(player.getUniqueId(), price); + return true; + } + + /** + * Will handle permission and payment of the button. + * If both permission and payment is handled successfully, it will handle the action. + * + * @param player The player to handle the permission and payment for. + * @return Whether the permission and payment was handled successfully. + */ + public boolean handleAll(Player player) { + boolean proceed = handlePermission(player) && handlePayment(player); + if (!proceed) + return false; + handleAction(player); + return true; + } + + /** + * @return The action of the button. Null if there is no action. + */ + @Nullable + public String getAction() { + return action; + } + + /** + * Will handle the action of the button. + * + * @param entity The entity to handle the action for. + */ + public void handleAction(Entity entity) { + if (action == null) + return; + Action fetch = BlobLibActionAPI.getInstance().getAction(action); + fetch.perform(entity); + } + + /** + * Will clone/copy the button to a new instance + * + * @return The new instance of the button. + */ + public InventoryButton copy() { + return new InventoryButton(key, new HashSet<>(slots), + permission, price, priceCurrency, action); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/inventory/InventoryHolderBuilder.java b/src/main/java/us/mytheria/bloblib/entities/inventory/InventoryHolderBuilder.java new file mode 100644 index 00000000..3b6226ee --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/inventory/InventoryHolderBuilder.java @@ -0,0 +1,207 @@ +package us.mytheria.bloblib.entities.inventory; + +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Objects; + +/** + * @author anjoismysign + *

+ * An InventoryHolderBuilder is an instance of a InventoryBuilder. + * It's a wrapper for InventoryHolder's using the BlobLib API. + */ +public class InventoryHolderBuilder extends InventoryBuilder { + private final InventoryHolder holder; + private HashMap defaultButtons; + + /** + * Constructs a InventoryHolderBuilder with a title, size, and a ButtonManager. + * + * @param title The title of the inventory. + * @param size The size of the inventory. + * @param buttonManager The ButtonManager that will be used to manage the buttons. + */ + protected InventoryHolderBuilder(@NotNull String title, int size, + @NotNull ButtonManager buttonManager, + @Nullable InventoryHolder holder) { + this.holder = holder; + this.setTitle(Objects.requireNonNull(title, + "'title' cannot be null!")); + this.setSize(size); + this.setButtonManager(Objects.requireNonNull(buttonManager, + "'buttonManager' cannot be null!")); + this.buildInventory(); + this.loadDefaultButtons(); + } + + /** + * Creates a new InventoryHolderBuilder in an empty constructor. + *

+ * I recommend you try constructing your InventoryHolderBuilder + * and get used to the API for a strong workflow. + * + * @param holder The inventory holder + */ + public InventoryHolderBuilder(InventoryHolder holder) { + this.holder = holder; + } + + /** + * Copies itself to a new reference. + * + * @return The cloned inventory + */ + @NotNull + public InventoryHolderBuilder copy() { + return new InventoryHolderBuilder<>(getTitle(), getSize(), getButtonManager(), getHolder()); + } + + /** + * Will create a new InventoryHolderBuilder with the same + * title, size, and ButtonManager with provided holder. + * + * @param holder The inventory holder + * @return The new InventoryHolderBuilder + */ + @NotNull + public InventoryHolderBuilder setHolder(@NotNull InventoryHolder holder) { + Objects.requireNonNull(holder, "'holder' cannot be null!"); + return new InventoryHolderBuilder<>(getTitle(), getSize(), getButtonManager(), holder); + } + + /** + * Adds a default button using the specified key. + * Default buttons are meant to persist inventory + * changes. + * + * @param key The key of the button + */ + public void addDefaultButton(String key) { + for (Integer i : getSlots(key)) { + addDefaultButton(key, getButton(i)); + break; + } + } + + /** + * Loads/reloads the default buttons. + */ + public void loadDefaultButtons() { + setDefaultButtons(new HashMap<>()); + ButtonManager manager = getButtonManager(); + getKeys().forEach(this::addDefaultButton); + } + + /** + * Adds a default button to the memory of the inventory. + * + * @param name The name of the button + * @param item The ItemStack of the button + */ + public void addDefaultButton(String name, ItemStack item) { + defaultButtons.put(name, item); + } + + /** + * Searches for the ItemStack that is linked to said key. + * + * @param key The key of the button + * @return The ItemStack + */ + @Nullable + public ItemStack getDefaultButton(String key) { + return defaultButtons.get(key); + } + + /** + * Searches for the ItemStack that is linked to said key. + * The ItemStack is cloned and then returned. + * + * @param key The key of the button + * @return The cloned ItemStack + */ + @Nullable + public ItemStack cloneDefaultButton(String key) { + return defaultButtons.get(key).clone(); + } + + /** + * Retrieves the default buttons + * + * @return The default buttons + */ + @NotNull + public HashMap getDefaultButtons() { + return defaultButtons; + } + + /** + * Sets the default buttons + * + * @param defaultButtons The default buttons to set + */ + public void setDefaultButtons(HashMap defaultButtons) { + this.defaultButtons = defaultButtons; + } + + /** + * Builds the inventory since by default its reference is null + */ + public void buildInventory() { + getButtonManager().getIntegerKeys().forEach(this::setButton); + } + + /** + * Retrieves the inventory + * + * @return The inventory if holder is not null, otherwise null. + */ + @Nullable + public Inventory getInventory() { + InventoryHolder holder = getHolder(); + if (holder == null) + return null; + return holder.getInventory(); + } + + /** + * Retrieves the inventory holder + * + * @return The inventory holder + */ + @Nullable + public InventoryHolder getHolder() { + return holder; + } + + /** + * Sets the button at the specified slot + * + * @param slot The slot to set the button at + * @param itemStack The ItemStack to set the button to + */ + public void setButton(int slot, ItemStack itemStack) { + getInventory().setItem(slot, itemStack); + } + + /** + * Refills all ItemStacks that belong to said key + * + * @param key The key of the button + */ + public void refillButton(String key) { + for (Integer i : getSlots(key)) { + setButton(i, getButton(i)); + } + } + + public void open(Player player) { + player.openInventory(getInventory()); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/inventory/MetaBlobButtonManager.java b/src/main/java/us/mytheria/bloblib/entities/inventory/MetaBlobButtonManager.java new file mode 100644 index 00000000..76e8d308 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/inventory/MetaBlobButtonManager.java @@ -0,0 +1,140 @@ +package us.mytheria.bloblib.entities.inventory; + +import me.anjoismysign.anjo.entities.Uber; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.inventory.ItemStack; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Set; + +/** + * @author anjoismysign + * A BlobButtonManager is a smart tool for GUIs. + * It allows you to store buttons as key-value pairs into which + * you can store a single ItemStack to multiple slots of an inventory. + * It also allows you to manage all the slots that this ItemStack is currently + * stored in. One feature of this class is that it allows you parse a ButtonManager + * from a YAML file. + *

+ * It handles the above through HashMaps giving it a O(1) time complexity. + */ +public class MetaBlobButtonManager extends ButtonManager { + /** + * Builds a ButtonManager through the specified ConfigurationSection. + * Uses HashMap to store buttons. + * + * @param section configuration section which contains all the buttons + * @return a non abstract ButtonManager. + */ + public static MetaBlobButtonManager fromConfigurationSection(ConfigurationSection section) { + MetaBlobButtonManager blobButtonManager = new MetaBlobButtonManager(); + blobButtonManager.add(section); + return blobButtonManager; + } + + /** + * Builds a non abstract ButtonManager without any buttons stored yet. + */ + public MetaBlobButtonManager() { + this(new ButtonManagerData<>(new HashMap<>(), + new HashMap<>())); + } + + @Override + public MetaBlobButtonManager copy() { + return new MetaBlobButtonManager(new ButtonManagerData<>(copyStringKeys(), + copyIntegerKeys())); + } + + private MetaBlobButtonManager(ButtonManagerData buttonManagerData) { + super(buttonManagerData); + } + + /** + * Checks if the specified key is stored in this ButtonManager + * + * @param key the key to check + * @return true if the specified key is stored in this ButtonManager + */ + @Override + public boolean contains(String key) { + return getStringKeys().containsKey(key); + } + + /** + * Checks if the specified slot is stored in this ButtonManager + * + * @param slot the slot to check + * @return true if the specified slot is stored in this ButtonManager + */ + @Override + public boolean contains(int slot) { + return getIntegerKeys().containsKey(slot); + } + + /** + * Gets all buttons stored in this ButtonManager that belong to the specified key + * + * @param key the key of the buttons + * @return all buttons stored in this ButtonManager that belong to the specified key + * null if the key is not stored in this ButtonManager + */ + @Override + public Set get(String key) { + if (contains(key)) { + return getStringKeys().get(key).getSlots(); + } + return null; + } + + /** + * returns the keys of all buttons stored in this ButtonManager + * + * @return keys of all buttons stored in this ButtonManager + */ + @Override + public Collection keys() { + return getStringKeys().keySet(); + } + + /** + * returns an ItemStack stored in this ButtonManager + * + * @param slot the slot of the ItemStack + * @return the ItemStack stored in this ButtonManager + */ + @Override + public ItemStack get(int slot) { + return getIntegerKeys().get(slot); + } + + /** + * returns all ItemStacks stored in this ButtonManager + * + * @return all ItemStacks in this ButtonManager + */ + @Override + public Collection buttons() { + return getIntegerKeys().values(); + } + + /** + * adds all buttons inside a configuration section through parsing + * + * @param section configuration section which contains all the buttons + * @return true if at least one button was succesfully added. + * this is determined in case the being called after the first add call + */ + @Override + public boolean add(ConfigurationSection section) { + Set set = section.getKeys(false); + Uber madeChanges = new Uber<>(false); + set.stream().filter(key -> !contains(key)).forEach(key -> { + madeChanges.talk(true); + MetaBlobMultiSlotable slotable = MetaBlobMultiSlotable.read(section.getConfigurationSection(key), key); + slotable.setInButtonManager(this); + }); + return madeChanges.thanks(); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/inventory/MetaBlobInventory.java b/src/main/java/us/mytheria/bloblib/entities/inventory/MetaBlobInventory.java new file mode 100644 index 00000000..ed018ac5 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/inventory/MetaBlobInventory.java @@ -0,0 +1,51 @@ +package us.mytheria.bloblib.entities.inventory; + +import me.anjoismysign.anjo.entities.Result; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +public class MetaBlobInventory extends SharableInventory { + private final String type; + + public static MetaBlobInventory fromInventoryBuilderCarrier(InventoryBuilderCarrier carrier) { + return new MetaBlobInventory(carrier.title(), carrier.size(), + carrier.buttonManager().copy(), carrier.type()); + } + + public MetaBlobInventory(@NotNull String title, int size, + @NotNull ButtonManager buttonManager, + @NotNull String type) { + super(title, size, buttonManager); + this.type = Objects.requireNonNull(type, "'type' cannot be null!"); + } + + @Override + @NotNull + public MetaBlobInventory copy() { + return new MetaBlobInventory(getTitle(), getSize(), + getButtonManager().copy(), getType()); + } + + /** + * Will filter between buttons and will check if they have a valid Meta. + * First button from this filter to contain the provided slot will be returned + * as a valid Result. An invalid Result will be returned if no candidate was found. + * + * @param slot The slot to check. + * @return The button that has a valid Meta and contains provided slot. + */ + @NotNull + public Result belongsToAMetaButton(int slot) { + MetaInventoryButton metaInventoryButton = + getKeys().stream().map(this::getButton).filter(MetaInventoryButton::hasMeta) + .filter(button -> button.getSlots().contains(slot)).findFirst() + .orElse(null); + return Result.ofNullable(metaInventoryButton); + } + + @NotNull + public String getType() { + return type; + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/inventory/MetaBlobInventoryHolder.java b/src/main/java/us/mytheria/bloblib/entities/inventory/MetaBlobInventoryHolder.java new file mode 100644 index 00000000..62f93bd8 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/inventory/MetaBlobInventoryHolder.java @@ -0,0 +1,57 @@ +package us.mytheria.bloblib.entities.inventory; + +import me.anjoismysign.anjo.entities.Result; +import org.bukkit.inventory.InventoryHolder; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +public class MetaBlobInventoryHolder extends InventoryHolderBuilder { + private final String type; + + public static MetaBlobInventoryHolder fromInventoryBuilderCarrier(InventoryBuilderCarrier carrier, @Nullable InventoryHolder holder) { + return new MetaBlobInventoryHolder(carrier.title(), carrier.size(), carrier.buttonManager(), carrier.type(), holder); + } + + public MetaBlobInventoryHolder(@NotNull String title, int size, + @NotNull ButtonManager buttonManager, + @NotNull String type, @Nullable InventoryHolder holder) { + super(title, size, buttonManager, null); + this.type = Objects.requireNonNull(type, "'type' cannot be null!"); + } + + @Override + @NotNull + public MetaBlobInventoryHolder copy() { + return new MetaBlobInventoryHolder(getTitle(), getSize(), getButtonManager(), getType(), getHolder()); + } + + @Override + @NotNull + public MetaBlobInventoryHolder setHolder(@NotNull InventoryHolder holder) { + return new MetaBlobInventoryHolder(getTitle(), getSize(), getButtonManager(), getType(), holder); + } + + /** + * Will filter between buttons and will check if they have a valid Meta. + * First button from this filter to contain the provided slot will be returned + * as a valid Result. An invalid Result will be returned if no candidate was found. + * + * @param slot The slot to check. + * @return The button that has a valid Meta and contains provided slot. + */ + @NotNull + public Result belongsToAMetaButton(int slot) { + MetaInventoryButton metaInventoryButton = + getKeys().stream().map(this::getButton).filter(MetaInventoryButton::hasMeta) + .filter(button -> button.getSlots().contains(slot)).findFirst() + .orElse(null); + return Result.ofNullable(metaInventoryButton); + } + + @NotNull + public String getType() { + return type; + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/inventory/MetaBlobMultiSlotable.java b/src/main/java/us/mytheria/bloblib/entities/inventory/MetaBlobMultiSlotable.java new file mode 100644 index 00000000..6c540d1a --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/inventory/MetaBlobMultiSlotable.java @@ -0,0 +1,220 @@ +package us.mytheria.bloblib.entities.inventory; + +import org.bukkit.Bukkit; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; +import us.mytheria.bloblib.itemstack.ItemStackReader; + +import java.util.HashSet; +import java.util.Set; + +/** + * @author anjoismysign + *

+ * BlobMultiSlotable is an instance of MultiSlotable which + * itself contains an extra attribute which would be + * a String 'key'. BlobMultiSlotable contains parsing + * methods from ConfigurationSection's and methods + * related to insertion into org.bukkit.inventory.Inventory + * or even us.mytheria.bloblib.entities.inventory.ButtonManager + * in case of working with BlobLib's GUI system. + * The 'key' attribute is used to identify the BlobMultiSlotable + * in case of failure to be able to detail/trace the error. + */ +public class MetaBlobMultiSlotable extends MultiSlotable { + private final String key; + private final String meta; + private final String subMeta; + + /** + * Parses/reads from a ConfigurationSection using ItemStackReader. + * + * @param section The ConfigurationSection to read from. + * @param key The key of the BlobMultiSlotable which was intended to read from. + * @return The BlobMultiSlotable which was read from the ConfigurationSection. + */ + public static MetaBlobMultiSlotable read(ConfigurationSection section, String key) { + ConfigurationSection itemStackSection = section.getConfigurationSection("ItemStack"); + if (itemStackSection == null) { + Bukkit.getLogger().severe("ItemStack section is null for " + key); + return null; + } + ItemStack itemStack = ItemStackReader.READ_OR_FAIL_FAST(itemStackSection).build(); + HashSet set = new HashSet<>(); + String read = section.getString("Slot", "-1"); + String[] slots = read.split(","); + if (slots.length != 1) { + for (String slot : slots) { + add(set, slot, section.getName()); + } + } else { + add(set, read, section.getName()); + } + String meta = section.getString("Meta", "NONE"); + String subMeta = null; + if (section.isString("SubMeta")) { + subMeta = section.getString("SubMeta"); + } + String permission = null; + if (section.isString("Permission")) { + permission = section.getString("Permission"); + } + double price = 0; + if (section.isDouble("Price")) { + price = section.getDouble("Price"); + } + String priceCurrency = null; + if (section.isString("Price-Currency")) { + priceCurrency = section.getString("Price-Currency"); + } + String action = null; + if (section.isString("Action")) + action = section.getString("Action"); + return new MetaBlobMultiSlotable(set, itemStack, key, meta, subMeta, + permission, price, priceCurrency, action); + } + + /** + * Constructor for BlobMultiSlotable + * + * @param slots The slots to add the item to + * @param itemStack The item to add + * @param key The key to use for the item + */ + public MetaBlobMultiSlotable(Set slots, ItemStack itemStack, String key, + String meta, @Nullable String subMeta, + @Nullable String permission, + double price, + @Nullable String priceCurrency, + @Nullable String action) { + super(slots, itemStack, permission, price, priceCurrency, action); + this.key = key; + this.meta = meta; + this.subMeta = subMeta; + } + + /** + * Will insert the BlobMultiSlotable into the given Inventory. + * The ItemStack is not cloned, so they all should be references + * in case of retrieving in the future. + * + * @param inventory The inventory to insert the ItemStacks + */ + public void setInInventory(Inventory inventory) { + for (Integer slot : getSlots()) { + inventory.setItem(slot, getItemStack()); + } + } + + public MetaInventoryButton toMetaInventoryButton() { + return new MetaInventoryButton(key, getSlots(), meta, subMeta, + getPermission(), getPrice(), getPriceCurrency(), getAction()); + } + + /** + * Writes in a ButtonManager (in case of working + * with BlobLib's GUI system) the BlobMultiSlotable + * + * @param buttonManager The ButtonManager to write in + * the BlobMultiSlotable + */ + public void setInButtonManager(ButtonManager buttonManager) { + buttonManager.getStringKeys().put(key, toMetaInventoryButton()); + for (Integer slot : getSlots()) { + buttonManager.getIntegerKeys().put(slot, getItemStack()); + } + } + + /** + * Adds slots to an existing set. Used in parsing + * from ConfigurationSection. + * + * @param set the set to add + * @param raw the raw string to parse. should be a range or a single number, i.e. 1-7 or 8 + * @param sectionName the name of the section, used for logging/debugging + */ + private static void add(Set set, String raw, String sectionName) { + String[] split = raw.split("-"); + switch (split.length) { + /* + if String.split(regex) has no match will return String itself + which is a length of "1". + Example for this case would be if 'raw' is "4" instead of "1-7" + */ + case 1 -> { + int slot = Integer.parseInt(split[0]); + if (slot < 0) { + Bukkit.getLogger().info(sectionName + " got a slot that's is smaller than 0."); + Bukkit.getLogger().info("This is not possible in an inventory so it was set"); + Bukkit.getLogger().info("to '0' which is default."); + slot = 0; + } + set.add(slot); + } + case 2 -> { + int start = 0; + try { + start = Integer.parseInt(split[0]); + } catch (NumberFormatException e) { + Bukkit.getLogger().info(sectionName + " got a slot that's not a number"); + Bukkit.getLogger().info("This is not possible in an inventory so it was set"); + Bukkit.getLogger().info("to '0' which is default."); + } + int end = 0; + try { + end = Integer.parseInt(split[1]); + } catch (NumberFormatException e) { + Bukkit.getLogger().info(sectionName + " got a slot that's not a number"); + Bukkit.getLogger().info("This is not possible in an inventory so it was set"); + Bukkit.getLogger().info("to '0' which is default."); + } + for (int i = start; i <= end; i++) { + set.add(i); + } + } + default -> { + Bukkit.getLogger().info("Invalid range inside inside " + sectionName); + Bukkit.getLogger().info("The range must be in the format of 'start-end' or 'number'"); + } + } + } + + /** + * @return The key/identifier of the BlobMultiSlotable + */ + public String getKey() { + return key; + } + + /** + * @return Whether the BlobMultiSlotable has meta or not + */ + public boolean hasMeta() { + return !meta.equals("NONE"); + } + + /** + * @return The meta of the BlobMultiSlotable + */ + public String getMeta() { + return meta; + } + + /** + * @return The subMeta of the BlobMultiSlotable + */ + @Nullable + public String getSubMeta() { + return subMeta; + } + + /** + * @return the slots that the ItemStack belongs to + */ + @Override + public Set getSlots() { + return (Set) super.getSlots(); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/inventory/MetaBlobPlayerInventoryBuilder.java b/src/main/java/us/mytheria/bloblib/entities/inventory/MetaBlobPlayerInventoryBuilder.java new file mode 100644 index 00000000..abcc418a --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/inventory/MetaBlobPlayerInventoryBuilder.java @@ -0,0 +1,31 @@ +package us.mytheria.bloblib.entities.inventory; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +public class MetaBlobPlayerInventoryBuilder extends PlayerInventoryBuilder { + + public static MetaBlobPlayerInventoryBuilder fromInventoryBuilderCarrier(InventoryBuilderCarrier carrier, @Nullable UUID holderId) { + return new MetaBlobPlayerInventoryBuilder(carrier.title(), carrier.size(), carrier.buttonManager(), holderId); + } + + public MetaBlobPlayerInventoryBuilder(@NotNull String title, int size, + @NotNull ButtonManager buttonManager, + @Nullable UUID holderId) { + super(title, size, buttonManager, holderId); + } + + @Override + @NotNull + public MetaBlobPlayerInventoryBuilder copy() { + return new MetaBlobPlayerInventoryBuilder(getTitle(), getSize(), getButtonManager(), getHolderId()); + } + + @Override + @NotNull + public MetaBlobPlayerInventoryBuilder setHolderId(@NotNull UUID holderId) { + return new MetaBlobPlayerInventoryBuilder(getTitle(), getSize(), getButtonManager(), holderId); + } +} \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/entities/inventory/MetaInventoryButton.java b/src/main/java/us/mytheria/bloblib/entities/inventory/MetaInventoryButton.java new file mode 100644 index 00000000..ad3b4b22 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/inventory/MetaInventoryButton.java @@ -0,0 +1,63 @@ +package us.mytheria.bloblib.entities.inventory; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashSet; +import java.util.Set; + +public class MetaInventoryButton extends InventoryButton { + private final String meta; + private final String subMeta; + + public static MetaInventoryButton fromInventoryButton(InventoryButton button, String meta, + String subMeta) { + return new MetaInventoryButton(button.getKey(), button.getSlots(), meta, + subMeta, button.getPermission(), button.getPrice(), + button.getPriceCurrency(), button.getAction()); + } + + public MetaInventoryButton(String key, Set slots, String meta, + @Nullable String subMeta, @Nullable String permission, + double price, @Nullable String priceCurrency, + @Nullable String action) { + super(key, slots, permission, price, priceCurrency, action); + this.meta = meta; + this.subMeta = subMeta; + } + + /** + * @return Whether the MetaInventoryButton has meta or not + */ + public boolean hasMeta() { + return !getMeta().equals("NONE"); + } + + /** + * @return The meta of the MetaInventoryButton + */ + @NotNull + public String getMeta() { + return meta; + } + + /** + * @return The subMeta of the MetaInventoryButton if it has one + * null otherwise + */ + @Nullable + public String getSubMeta() { + return subMeta; + } + + /** + * Will clone/copy the button to a new instance + * + * @return The new instance of the button. + */ + @Override + public MetaInventoryButton copy() { + return new MetaInventoryButton(getKey(), new HashSet<>(getSlots()), getMeta(), getSubMeta(), + getPermission(), getPrice(), getPriceCurrency(), getAction()); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/inventory/MultiSlotable.java b/src/main/java/us/mytheria/bloblib/entities/inventory/MultiSlotable.java new file mode 100644 index 00000000..14576c98 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/inventory/MultiSlotable.java @@ -0,0 +1,78 @@ +package us.mytheria.bloblib.entities.inventory; + +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; + +/** + * @author anjoismysign + *

+ * A MultiSlotable is a concept of an ItemStack which can + * be placed in multiple slots, lets say slots of an + * inventory/GUI. + */ +public abstract class MultiSlotable { + private final Collection slots; + private final ItemStack itemStack; + private final String permission; + private final double price; + private final String priceCurrency; + private final String action; + + /** + * @param slots The slots to be used. + * @param itemStack The ItemStack to be used. + * @param permission The permission to be used. + * @param price The price to be used. + * @param priceCurrency The price currency to be used. + * @param action The action to be used. + */ + public MultiSlotable(Collection slots, ItemStack itemStack, + @Nullable String permission, + double price, + @Nullable String priceCurrency, + @Nullable String action) { + this.slots = slots; + this.itemStack = itemStack; + this.permission = permission; + this.price = price; + this.priceCurrency = priceCurrency; + this.action = action; + } + + /** + * Retrieves the slots of which this MultiSlotable is linked to. + * + * @return The slots of which this MultiSlotable is linked to. + */ + public Collection getSlots() { + return slots; + } + + /** + * @return The ItemStack of this MultiSlotable. + */ + public ItemStack getItemStack() { + return itemStack; + } + + @Nullable + public String getPermission() { + return permission; + } + + public double getPrice() { + return price; + } + + @Nullable + public String getPriceCurrency() { + return priceCurrency; + } + + @Nullable + public String getAction() { + return action; + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/inventory/MultiSuperFurnace.java b/src/main/java/us/mytheria/bloblib/entities/inventory/MultiSuperFurnace.java new file mode 100644 index 00000000..ebec1f0f --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/inventory/MultiSuperFurnace.java @@ -0,0 +1,262 @@ +package us.mytheria.bloblib.entities.inventory; + +import me.anjoismysign.anjo.entities.Result; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import us.mytheria.bloblib.Experience; +import us.mytheria.bloblib.FuelAPI; +import us.mytheria.bloblib.entities.Fuel; +import us.mytheria.bloblib.entities.FurnaceOperation; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author anjoismysign + *

+ * A MultiSuperFurnace is an instance of a SharableInventory. + * It's a custom Furnace inventory that automatically smelts items. + * Each operation will have a fuel size. + * In order to make an operation, see {@link #handle()}. + * The difference between a normal SuperFurnace and a MultiSuperFurnace is that + * a MultiSuperFurnace can have multiple fuel, input and output slots. + */ +public class MultiSuperFurnace extends SharableInventory { + private T fuelButton; + private T inputButton; + private T outputButton; + private long operationSize = 200; + private long storage = 0; + private Map operationHistory; + private final FuelAPI fuelAPI; + + public static MultiSuperFurnace + fromInventoryBuilderCarrier(InventoryBuilderCarrier carrier, + long operationSize) { + ButtonManager buttonManager = carrier.buttonManager(); + T fuelButton = buttonManager.getButton("Fuel"); + if (fuelButton == null) + return null; + + T inputButton = buttonManager.getButton("Input"); + if (inputButton == null) + return null; + + T outputButton = buttonManager.getButton("Output"); + if (outputButton == null) + return null; + int outputSlot = outputButton.getSlots().stream().findFirst() + .orElseThrow(() -> new NoSuchElementException( + "Inventory '" + carrier.title() + "' has an empty " + + "Output button!")); + return new MultiSuperFurnace<>(carrier.title(), carrier.size(), + buttonManager, operationSize, fuelButton, inputButton, outputButton); + } + + /** + * Constructs a InventoryHolderBuilder with a title, size, and a ButtonManager. + * + * @param title The title of the inventory. + * @param size The size of the inventory. + * @param buttonManager The ButtonManager that will be used to manage the buttons. + */ + protected MultiSuperFurnace(@NotNull String title, int size, + @NotNull ButtonManager buttonManager, + long operationSize, + @NotNull T fuelButton, + @NotNull T inputButton, + @NotNull T outputButton) { + this.setTitle(Objects.requireNonNull(title, + "'title' cannot be null!")); + this.setSize(size); + this.setButtonManager(Objects.requireNonNull(buttonManager, + "'buttonManager' cannot be null!")); + this.buildInventory(); + this.loadDefaultButtons(); + this.operationHistory = new HashMap<>(); + this.operationSize = operationSize; + this.fuelButton = fuelButton; + this.inputButton = inputButton; + this.outputButton = outputButton; + this.fuelAPI = FuelAPI.getInstance(); + } + + /** + * Creates a new InventoryHolderBuilder in an empty constructor. + * Default operation size is 200. + *

+ * I recommend you try constructing your InventoryHolderBuilder + * and get used to the API for a strong workflow. + */ + public MultiSuperFurnace() { + this.fuelAPI = FuelAPI.getInstance(); + } + + /** + * Copies itself to a new reference. + * + * @return The cloned inventory + */ + @NotNull + public MultiSuperFurnace copy() { + return new MultiSuperFurnace<>(getTitle(), getSize(), + getButtonManager().copy(), getOperationSize(), + getFuelButton(), getInputButton(), getOutputButton()); + } + + /** + * Will return the size of fuel required for each operation. + * + * @return The size of fuel required for each operation. + */ + public long getOperationSize() { + return operationSize; + } + + /** + * Will return the fuel stored in the current super furnace. + * + * @return The fuel stored in the current super furnace. + */ + public long getStorage() { + return storage; + } + + /** + * Will attempt to smelt all input items with all provided fuel. + * If at least one input item cannot be smelted, the operation will fail. + * + * @param fuel The fuel to use. + * @param input The input items to smelt. + * @return The result of the operation. + */ + @Nullable + public List smelt(ItemStack[] fuel, ItemStack[] input) { + List list = new ArrayList<>(); + long operationSize = this.operationSize; + for (ItemStack inputItem : input) { + operationSize += inputItem.getAmount() * this.operationSize; + } + int compare = Long.compare(storage, operationSize); + if (compare <= 0) { + for (ItemStack fuelItem : fuel) { + Result isFuel = fuelAPI.isFuel(fuelItem.getType()); + if (!isFuel.isValid()) + continue; + Fuel value = isFuel.value(); + long ticks = value.getBurnTime() * fuelItem.getAmount(); + storage += ticks; + } + } + compare = Long.compare(storage, operationSize); + if (compare == -1) + return null; + for (ItemStack inputItem : input) { + list.add(FurnaceOperation.vanilla(inputItem)); + } + if (!canHandle(list.size())) + return null; + return list; + } + + /** + * @return Gets the fuel ItemStacks, including empty slots. + */ + public ItemStack[] getFuel() { + return fuelButton.getSlots().stream().map(this::getButton).toArray(ItemStack[]::new); + } + + /** + * @return Gets the input ItemStacks, including empty slots. + */ + public ItemStack[] getInput() { + return inputButton.getSlots().stream().map(this::getButton).toArray(ItemStack[]::new); + } + + /** + * @return Gets the output slots, including non-empty slots. + */ + public Set getOutputSlots() { + return outputButton.getSlots(); + } + + public boolean canHandle(int amount) { + return getOutputSlots().stream().map(this::getButton).filter(itemStack -> itemStack == null + || itemStack != null && itemStack.getType().isAir()).count() >= amount; + } + + /** + * Will attempt to smelt the input item with the fuel. + * + * @return True if the operation was successful. False otherwise. + */ + public boolean handle() { + Set available = getAvailableSlots(); + ItemStack[] fuel = getFuel(); + ItemStack[] input = getInput(); + + List operations = smelt(fuel, input); + if (operations == null) + return false; + operations.forEach(operation -> { + int slot = available.stream().findFirst().orElseThrow(); + available.remove(slot); + operationHistory.put(slot, operation); + ItemStack output = operation.result().clone(); + setButton(slot, output); + }); + return true; + } + + /** + * @return The available slots for the next operation. + */ + @NotNull + public Set getAvailableSlots() { + Set dupe = new HashSet<>(getOutputSlots()); + dupe.removeAll(operationHistory.keySet()); + dupe = dupe.stream().sorted().collect(Collectors.toCollection(LinkedHashSet::new)); + return dupe; + } + + /** + * @return The fuel button. + */ + @NotNull + public T getFuelButton() { + return this.fuelButton; + } + + /** + * @return The input button. + */ + @NotNull + public T getInputButton() { + return this.inputButton; + } + + /** + * @return The output button. + */ + @NotNull + public T getOutputButton() { + return this.outputButton; + } + + /** + * Will apply the experience to the player and remove the operation from the history. + * + * @param player The player to apply the experience to. + * @param slot The slot of the operation. + */ + public void apply(Player player, int slot) { + FurnaceOperation operation = operationHistory.get(slot); + if (operation == null) + return; + float experience = operation.experience(); + Experience.changeExp(player, experience); + operationHistory.remove(slot); + } +} \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/entities/inventory/ObjectBuilder.java b/src/main/java/us/mytheria/bloblib/entities/inventory/ObjectBuilder.java index 424f0511..e57eb4a4 100644 --- a/src/main/java/us/mytheria/bloblib/entities/inventory/ObjectBuilder.java +++ b/src/main/java/us/mytheria/bloblib/entities/inventory/ObjectBuilder.java @@ -117,7 +117,11 @@ public void updateDefaultButton(String key, String regex, String replacement) { */ public void modifyDefaultButton(String key, Function function) { - this.getSlots(key).forEach(i -> { + Set slots = getSlots(key); + if (slots == null) + throw new NullPointerException("'" + key + "' is not a valid button key" + + "inside '" + getTitle() + "' inventory"); + slots.forEach(i -> { ItemStack itemStack = cloneDefaultButton(key); ItemStackModder modder = ItemStackModder.mod(itemStack); function.apply(modder); @@ -322,6 +326,18 @@ public ObjectBuilder addQuickBlockButton(String buttonKey, long timeout) { return addObjectBuilderButton(ObjectBuilderButtonBuilder.QUICK_BLOCK(buttonKey, timeout, this)); } + /** + * Add a quick block button. + * Will use the block above selection. + * + * @param buttonKey the button key + * @param timeout the timeout + * @return this + */ + public ObjectBuilder addQuickAboveBlockButton(String buttonKey, long timeout) { + return addObjectBuilderButton(ObjectBuilderButtonBuilder.QUICK_ABOVE_BLOCK(buttonKey, timeout, this)); + } + /** * Add a quick item button. * @@ -516,6 +532,43 @@ public ObjectBuilder addPositiveDoubleButton(String buttonKey, long timeout) return addObjectBuilderButton(ObjectBuilderButtonBuilder.POSITIVE_DOUBLE(buttonKey, timeout, this)); } + /** + * A quick navigator for booleans. + * Value cannot be empty nor null. + * By default, the value is false. + * + * @param buttonKey The key of the button + * @return The button + */ + public ObjectBuilder addBoolean(String buttonKey) { + return addObjectBuilderButton(ObjectBuilderButtonBuilder.BOOLEAN(buttonKey, this)); + } + + /** + * A quick navigator for booleans. + * Value cannot be empty nor null. + * By default, the value is true. + * + * @param buttonKey The key of the button + * @return The button + */ + public ObjectBuilder addBooleanDefaultTrue(String buttonKey) { + return addObjectBuilderButton(ObjectBuilderButtonBuilder.BOOLEAN_DEFAULT_TRUE(buttonKey, this)); + } + + /** + * A quick navigator for any type of array. + * Value cannot be empty nor null. + * By default, the value is the first element of the array. + * + * @param buttonKey The key of the button + * @param enumClass The enum class + * @return The button + */ + public > ObjectBuilder addEnumNavigator(String buttonKey, Class enumClass) { + return addObjectBuilderButton(ObjectBuilderButtonBuilder.ENUM_NAVIGATOR(buttonKey, enumClass, this)); + } + /** * Set the function to be called when the build button is clicked * diff --git a/src/main/java/us/mytheria/bloblib/entities/inventory/ObjectBuilderButton.java b/src/main/java/us/mytheria/bloblib/entities/inventory/ObjectBuilderButton.java index d2d73288..16a5e7cf 100644 --- a/src/main/java/us/mytheria/bloblib/entities/inventory/ObjectBuilderButton.java +++ b/src/main/java/us/mytheria/bloblib/entities/inventory/ObjectBuilderButton.java @@ -1,7 +1,9 @@ package us.mytheria.bloblib.entities.inventory; +import org.bukkit.Bukkit; import org.bukkit.entity.Player; -import us.mytheria.bloblib.BlobLibAssetAPI; +import org.jetbrains.annotations.Nullable; +import us.mytheria.bloblib.api.BlobLibSoundAPI; import us.mytheria.bloblib.entities.message.BlobSound; import java.util.Optional; @@ -14,6 +16,12 @@ public abstract class ObjectBuilderButton { private final BiConsumer, Player> listenerBiConsumer; private final Function function; + /** + * @param buttonKey The key/reference of the button inside the Inventory + * @param defaultValue The default value of the button + * @param listenerBiConsumer The listener that will be called when the button is clicked + * @param function The function that will be called when the listener is finished + */ protected ObjectBuilderButton(String buttonKey, Optional defaultValue, BiConsumer, Player> listenerBiConsumer, Function function) { @@ -31,6 +39,7 @@ public Optional get() { return value; } + @Nullable public T orNull() { return value.orElse(null); } @@ -48,14 +57,20 @@ public void addListener(Player player, Optional clickSound) { } public void addListener(Player player) { - addListener(player, Optional.of(BlobLibAssetAPI.getSound("Builder.Button-Click"))); + addListener(player, Optional.of(BlobLibSoundAPI.getInstance().getSound("Builder.Button-Click"))); } public boolean isValuePresent() { return value.isPresent(); } - public boolean isValuePresentAndNotNull() { + public boolean isValuePresentAndNotNull(boolean debug) { + if (debug) + Bukkit.getLogger().info(buttonKey + ": value is present: " + value.isPresent() + " and value is not null: " + (value.get() != null)); return value.isPresent() && value.get() != null; } + + public boolean isValuePresentAndNotNull() { + return isValuePresentAndNotNull(false); + } } \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/entities/inventory/ObjectBuilderButtonBuilder.java b/src/main/java/us/mytheria/bloblib/entities/inventory/ObjectBuilderButtonBuilder.java index 4d3b1f3e..2c0fd107 100644 --- a/src/main/java/us/mytheria/bloblib/entities/inventory/ObjectBuilderButtonBuilder.java +++ b/src/main/java/us/mytheria/bloblib/entities/inventory/ObjectBuilderButtonBuilder.java @@ -4,9 +4,13 @@ import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; import org.bukkit.inventory.ItemStack; import us.mytheria.bloblib.BlobLibAPI; -import us.mytheria.bloblib.BlobLibAssetAPI; +import us.mytheria.bloblib.api.BlobLibListenerAPI; +import us.mytheria.bloblib.api.BlobLibMessageAPI; +import us.mytheria.bloblib.entities.ArrayNavigator; +import us.mytheria.bloblib.entities.BlobEditor; import us.mytheria.bloblib.entities.message.ReferenceBlobMessage; import us.mytheria.bloblib.utilities.BukkitUtil; import us.mytheria.bloblib.utilities.ItemStackUtil; @@ -15,7 +19,9 @@ import java.util.function.Consumer; import java.util.function.Function; +@SuppressWarnings({"ConcatenationWithEmptyString", "unused"}) public class ObjectBuilderButtonBuilder { + private static final BlobLibListenerAPI api = BlobLibAPI.getInstance().getListenerAPI(); /** * An ObjectBuilderButton builder for String's. @@ -32,7 +38,7 @@ public static ObjectBuilderButton STRING(String buttonKey, long timeout, String timerMessageKey, Function function) { ObjectBuilderButton objectBuilder = new ObjectBuilderButton<>(buttonKey, Optional.empty(), - (button, player) -> BlobLibAPI.addChatListener(player, timeout, + (button, player) -> api.addChatListener(player, timeout, string -> { if (string.equalsIgnoreCase("null")) { button.set(null); @@ -109,7 +115,7 @@ public static ObjectBuilderButton BYTE(String buttonKey, long timeout, Function function, boolean nullable) { ObjectBuilderButton objectBuilderButton = new ObjectBuilderButton<>(buttonKey, Optional.empty(), - (button, player) -> BlobLibAPI.addChatListener(player, timeout, string -> { + (button, player) -> api.addChatListener(player, timeout, string -> { try { if (nullable) { if (string.equalsIgnoreCase("null")) { @@ -121,7 +127,7 @@ public static ObjectBuilderButton BYTE(String buttonKey, long timeout, if (function.apply(input)) button.set(input); } catch (NumberFormatException e) { - BlobLibAssetAPI.getMessage("Builder.Number-Exception").sendAndPlay(player); + BlobLibMessageAPI.getInstance().getMessage("Builder.Number-Exception").handle(player); } }, timeoutMessageKey, timerMessageKey), function) { @@ -209,7 +215,7 @@ public static ObjectBuilderButton POSITIVE_BYTE(String buttonKey, long tim return true; }; ObjectBuilderButton objectBuilderButton = new ObjectBuilderButton<>(buttonKey, Optional.empty(), - (button, player) -> BlobLibAPI.addChatListener(player, timeout, string -> { + (button, player) -> api.addChatListener(player, timeout, string -> { try { byte input = Byte.parseByte(string); if (input < 0) { @@ -218,7 +224,7 @@ public static ObjectBuilderButton POSITIVE_BYTE(String buttonKey, long tim button.set(input); } } catch (NumberFormatException e) { - BlobLibAssetAPI.getMessage("Builder.Number-Exception").sendAndPlay(player); + BlobLibMessageAPI.getInstance().getMessage("Builder.Number-Exception").handle(player); } }, "Builder." + buttonKey + "-Timeout", "Builder." + buttonKey), function) { @@ -243,7 +249,7 @@ public static ObjectBuilderButton SHORT(String buttonKey, long timeout, Function function, boolean nullable) { ObjectBuilderButton objectBuilderButton = new ObjectBuilderButton<>(buttonKey, Optional.empty(), - (button, player) -> BlobLibAPI.addChatListener(player, timeout, string -> { + (button, player) -> api.addChatListener(player, timeout, string -> { try { if (nullable) { if (string.equalsIgnoreCase("null")) { @@ -255,7 +261,7 @@ public static ObjectBuilderButton SHORT(String buttonKey, long timeout, if (function.apply(input)) button.set(input); } catch (NumberFormatException e) { - BlobLibAssetAPI.getMessage("Builder.Number-Exception").sendAndPlay(player); + BlobLibMessageAPI.getInstance().getMessage("Builder.Number-Exception").handle(player); } }, timeoutMessageKey, timerMessageKey), function) { }; @@ -341,7 +347,7 @@ public static ObjectBuilderButton POSITIVE_SHORT(String buttonKey, long t return true; }; ObjectBuilderButton objectBuilderButton = new ObjectBuilderButton<>(buttonKey, Optional.empty(), - (button, player) -> BlobLibAPI.addChatListener(player, timeout, string -> { + (button, player) -> api.addChatListener(player, timeout, string -> { try { short input = Short.parseShort(string); if (input < 0) { @@ -350,7 +356,7 @@ public static ObjectBuilderButton POSITIVE_SHORT(String buttonKey, long t button.set(input); } } catch (NumberFormatException e) { - BlobLibAssetAPI.getMessage("Builder.Number-Exception").sendAndPlay(player); + BlobLibMessageAPI.getInstance().getMessage("Builder.Number-Exception").handle(player); } }, "Builder." + buttonKey + "-Timeout", "Builder." + buttonKey), function) { @@ -375,7 +381,7 @@ public static ObjectBuilderButton INTEGER(String buttonKey, long timeou Function function, boolean nullable) { ObjectBuilderButton objectBuilder = new ObjectBuilderButton<>(buttonKey, Optional.empty(), - (button, player) -> BlobLibAPI.addChatListener(player, timeout, string -> { + (button, player) -> api.addChatListener(player, timeout, string -> { try { if (nullable) { if (string.equalsIgnoreCase("null")) { @@ -387,7 +393,7 @@ public static ObjectBuilderButton INTEGER(String buttonKey, long timeou if (function.apply(input)) button.set(input); } catch (NumberFormatException ignored) { - BlobLibAssetAPI.getMessage("Builder.Number-Exception").sendAndPlay(player); + BlobLibMessageAPI.getInstance().getMessage("Builder.Number-Exception").handle(player); } }, timeoutMessageKey, timerMessageKey), function) { }; @@ -474,7 +480,7 @@ public static ObjectBuilderButton POSITIVE_INTEGER(String buttonKey, lo return true; }; ObjectBuilderButton objectBuilderButton = new ObjectBuilderButton<>(buttonKey, Optional.empty(), - (button, player) -> BlobLibAPI.addChatListener(player, timeout, string -> { + (button, player) -> api.addChatListener(player, timeout, string -> { try { int input = Integer.parseInt(string); if (input < 0) { @@ -483,7 +489,7 @@ public static ObjectBuilderButton POSITIVE_INTEGER(String buttonKey, lo button.set(input); } } catch (NumberFormatException e) { - BlobLibAssetAPI.getMessage("Builder.Number-Exception").sendAndPlay(player); + BlobLibMessageAPI.getInstance().getMessage("Builder.Number-Exception").handle(player); } }, "Builder." + buttonKey + "-Timeout", "Builder." + buttonKey), function) { @@ -508,7 +514,7 @@ public static ObjectBuilderButton LONG(String buttonKey, long timeout, Function function, boolean nullable) { ObjectBuilderButton objectBuilder = new ObjectBuilderButton<>(buttonKey, Optional.empty(), - (button, player) -> BlobLibAPI.addChatListener(player, timeout, string -> { + (button, player) -> api.addChatListener(player, timeout, string -> { try { if (nullable) { if (string.equalsIgnoreCase("null")) { @@ -520,7 +526,7 @@ public static ObjectBuilderButton LONG(String buttonKey, long timeout, if (function.apply(input)) button.set(input); } catch (NumberFormatException ignored) { - BlobLibAssetAPI.getMessage("Builder.Number-Exception").sendAndPlay(player); + BlobLibMessageAPI.getInstance().getMessage("Builder.Number-Exception").handle(player); } }, timeoutMessageKey, timerMessageKey), function) { }; @@ -607,7 +613,7 @@ public static ObjectBuilderButton POSITIVE_LONG(String buttonKey, long tim return true; }; ObjectBuilderButton objectBuilderButton = new ObjectBuilderButton<>(buttonKey, Optional.empty(), - (button, player) -> BlobLibAPI.addChatListener(player, timeout, string -> { + (button, player) -> api.addChatListener(player, timeout, string -> { try { long input = Long.parseLong(string); if (input < 0) { @@ -616,7 +622,7 @@ public static ObjectBuilderButton POSITIVE_LONG(String buttonKey, long tim button.set(input); } } catch (NumberFormatException e) { - BlobLibAssetAPI.getMessage("Builder.Number-Exception").sendAndPlay(player); + BlobLibMessageAPI.getInstance().getMessage("Builder.Number-Exception").handle(player); } }, "Builder." + buttonKey + "-Timeout", "Builder." + buttonKey), function) { @@ -641,7 +647,7 @@ public static ObjectBuilderButton FLOAT(String buttonKey, long timeout, Function function, boolean nullable) { ObjectBuilderButton objectBuilder = new ObjectBuilderButton<>(buttonKey, Optional.empty(), - (button, player) -> BlobLibAPI.addChatListener(player, timeout, string -> { + (button, player) -> api.addChatListener(player, timeout, string -> { try { if (nullable) { if (string.equalsIgnoreCase("null")) { @@ -653,7 +659,7 @@ public static ObjectBuilderButton FLOAT(String buttonKey, long timeout, if (function.apply(input)) button.set(input); } catch (NumberFormatException ignored) { - BlobLibAssetAPI.getMessage("Builder.Number-Exception").sendAndPlay(player); + BlobLibMessageAPI.getInstance().getMessage("Builder.Number-Exception").handle(player); } }, timeoutMessageKey, timerMessageKey), function) { }; @@ -740,7 +746,7 @@ public static ObjectBuilderButton POSITIVE_FLOAT(String buttonKey, long t return true; }; ObjectBuilderButton objectBuilderButton = new ObjectBuilderButton<>(buttonKey, Optional.empty(), - (button, player) -> BlobLibAPI.addChatListener(player, timeout, string -> { + (button, player) -> api.addChatListener(player, timeout, string -> { try { float input = Float.parseFloat(string); if (input < 0) { @@ -749,7 +755,7 @@ public static ObjectBuilderButton POSITIVE_FLOAT(String buttonKey, long t button.set(input); } } catch (NumberFormatException e) { - BlobLibAssetAPI.getMessage("Builder.Number-Exception").sendAndPlay(player); + BlobLibMessageAPI.getInstance().getMessage("Builder.Number-Exception").handle(player); } }, "Builder." + buttonKey + "-Timeout", "Builder." + buttonKey), function) { @@ -774,7 +780,7 @@ public static ObjectBuilderButton DOUBLE(String buttonKey, long timeout, Function function, boolean nullable) { ObjectBuilderButton objectBuilder = new ObjectBuilderButton<>(buttonKey, Optional.empty(), - (button, player) -> BlobLibAPI.addChatListener(player, timeout, string -> { + (button, player) -> api.addChatListener(player, timeout, string -> { try { if (nullable) { if (string.equalsIgnoreCase("null")) { @@ -786,7 +792,7 @@ public static ObjectBuilderButton DOUBLE(String buttonKey, long timeout, if (function.apply(input)) button.set(input); } catch (NumberFormatException ignored) { - BlobLibAssetAPI.getMessage("Builder.Number-Exception").sendAndPlay(player); + BlobLibMessageAPI.getInstance().getMessage("Builder.Number-Exception").handle(player); } }, timeoutMessageKey, timerMessageKey), function) { }; @@ -872,7 +878,7 @@ public static ObjectBuilderButton POSITIVE_DOUBLE(String buttonKey, long return true; }; ObjectBuilderButton objectBuilderButton = new ObjectBuilderButton<>(buttonKey, Optional.empty(), - (button, player) -> BlobLibAPI.addChatListener(player, timeout, string -> { + (button, player) -> api.addChatListener(player, timeout, string -> { try { double input = Double.parseDouble(string); if (input < 0) { @@ -881,7 +887,7 @@ public static ObjectBuilderButton POSITIVE_DOUBLE(String buttonKey, long button.set(input); } } catch (NumberFormatException e) { - BlobLibAssetAPI.getMessage("Builder.Number-Exception").sendAndPlay(player); + BlobLibMessageAPI.getInstance().getMessage("Builder.Number-Exception").handle(player); } }, "Builder." + buttonKey + "-Timeout", "Builder." + buttonKey), function) { @@ -905,13 +911,38 @@ public static ObjectBuilderButton BLOCK(String buttonKey, long timeout, String timerMessageKey, Function function) { ObjectBuilderButton objectBuilderButton = new ObjectBuilderButton<>(buttonKey, Optional.empty(), - (button, player) -> BlobLibAPI.addPositionListener(player, timeout, + (button, player) -> api.addPositionListener(player, timeout, button::set, timeoutMessageKey, timerMessageKey), function) { }; function.apply(null); return objectBuilderButton; } + /** + * An ObjectBuilderButton builder for Block's. + * Will select the block above the clicked block. + * + * @param buttonKey The key of the button + * @param timeout The timeout of the chat listener + * @param timeoutMessageKey The key of the timeout message + * @param timerMessageKey The key of the timer message + * @param function The function to be executed when the button is clicked + * @return The button + */ + public static ObjectBuilderButton ABOVE_BLOCK(String buttonKey, long timeout, + String timeoutMessageKey, + String timerMessageKey, + Function function) { + ObjectBuilderButton objectBuilderButton = new ObjectBuilderButton<>(buttonKey, Optional.empty(), + (button, player) -> api.addPositionListener(player, timeout, + block -> { + button.set(block.getRelative(BlockFace.UP)); + }, timeoutMessageKey, timerMessageKey), function) { + }; + function.apply(null); + return objectBuilderButton; + } + /** * A simple ObjectBuilderButton for Block's. * @@ -953,13 +984,46 @@ public static ObjectBuilderButton QUICK_BLOCK(String buttonKey, long time + "-Timeout", "Builder." + buttonKey, block -> { objectBuilder.updateDefaultButton(buttonKey, "%" + placeholderRegex + "%", - BukkitUtil.printLocation(block.getLocation())); + block == null ? "N/A" : BukkitUtil.printLocation(block.getLocation())); objectBuilder.openInventory(); return true; }); } + /** + * A quick ObjectBuilderButton for Block's. + * Will select block above the clicked block. + * An example of how to use it: + *

+     *     ObjectBuilder<Person> objectBuilder = someRandomObjectBuilderYouHave;
+     *
+     *     ObjectBuilderButton<Block> button =
+     *     ObjectBuilderButtonBuilder.QUICK_BLOCK("Spawn", 300, objectBuilder);
+     *     //if block is null, default button will display "N/A"
+     *     
+ * + * @param buttonKey The key of the button + * The timeoutmessagekey is "Builder." + buttonKey + "-Timeout" + * The timermessagekey is "Builder." + buttonKey + * @param timeout The timeout of the chat listener + * @param objectBuilder The object builder + * @return The button + */ + public static ObjectBuilderButton QUICK_ABOVE_BLOCK(String buttonKey, long timeout, + ObjectBuilder objectBuilder) { + + String placeholderRegex = NamingConventions.toCamelCase(buttonKey); + return ABOVE_BLOCK(buttonKey, timeout, "Builder." + buttonKey + + "-Timeout", "Builder." + buttonKey, + block -> { + objectBuilder.updateDefaultButton(buttonKey, "%" + placeholderRegex + "%", + block == null ? "N/A" : BukkitUtil.printLocation(block.getLocation())); + objectBuilder.openInventory(); + return true; + }); + } + /** * A quick ObjectBuilderButton for Block's that accepts * a consumer when input is given. @@ -980,7 +1044,7 @@ public static ObjectBuilderButton QUICK_ACTION_BLOCK(String buttonKey, block -> { consumer.accept(block); objectBuilder.updateDefaultButton(buttonKey, "%" + placeholderRegex + "%", - BukkitUtil.printLocation(block.getLocation())); + block == null ? "N/A" : BukkitUtil.printLocation(block.getLocation())); objectBuilder.openInventory(); return true; }); @@ -998,7 +1062,7 @@ public static ObjectBuilderButton ITEM(String buttonKey, String timerMessageKey, Function function) { ObjectBuilderButton objectBuilderButton = new ObjectBuilderButton<>(buttonKey, Optional.empty(), - (button, player) -> BlobLibAPI.addDropListener(player, + (button, player) -> api.addDropListener(player, button::set, timerMessageKey), function) { }; function.apply(null); @@ -1080,7 +1144,7 @@ public static ObjectBuilderButton SELECTOR(String buttonKey, Function function, VariableSelector selector) { ObjectBuilderButton objectBuilderButton = new ObjectBuilderButton<>(buttonKey, Optional.empty(), - (button, player) -> BlobLibAPI.addSelectorListener(player, + (button, player) -> api.addSelectorListener(player, button::set, timerMessageKey, selector), function) { }; function.apply(null); @@ -1177,9 +1241,9 @@ public static ObjectBuilderButton MESSAGE(String buttonKey String timerMessageKey, Function function) { ObjectBuilderButton objectBuilderButton = new ObjectBuilderButton<>(buttonKey, Optional.empty(), - (button, player) -> BlobLibAPI.addChatListener(player, timeout, + (button, player) -> api.addChatListener(player, timeout, string -> { - ReferenceBlobMessage message = BlobLibAssetAPI.getMessage(string); + ReferenceBlobMessage message = BlobLibMessageAPI.getInstance().getMessage(string); button.set(message); }, timeoutMessageKey, @@ -1273,7 +1337,7 @@ public static ObjectBuilderButton WORLD(String buttonKey, long timeout, String timerMessageKey, Function function) { ObjectBuilderButton objectBuilderButton = new ObjectBuilderButton<>(buttonKey, Optional.empty(), - (button, player) -> BlobLibAPI.addChatListener(player, timeout, + (button, player) -> api.addChatListener(player, timeout, string -> { World world = Bukkit.getWorld(string); button.set(world); @@ -1353,4 +1417,118 @@ public static ObjectBuilderButton QUICK_ACTION_WORLD(String buttonKey, return true; }); } -} + + /** + * A quick navigator for booleans. + * Value cannot be empty nor null. + * By default, the value is false. + * + * @param buttonKey The key of the button + * @param objectBuilder The object builder + * @return The button + */ + public static ObjectBuilderButton BOOLEAN(String buttonKey, + ObjectBuilder objectBuilder) { + String placeholderRegex = NamingConventions.toCamelCase(buttonKey); + return NAVIGATOR(buttonKey, new Boolean[]{false, true}, value -> { + objectBuilder.updateDefaultButton(buttonKey, "%" + placeholderRegex + "%", + value + ""); + objectBuilder.openInventory(); + return true; + }); + } + + /** + * A quick navigator for booleans. + * Value cannot be empty nor null. + * By default, the value is true. + * + * @param buttonKey The key of the button + * @param objectBuilder The object builder + * @return The button + */ + public static ObjectBuilderButton BOOLEAN_DEFAULT_TRUE(String buttonKey, + ObjectBuilder objectBuilder) { + String placeholderRegex = NamingConventions.toCamelCase(buttonKey); + return NAVIGATOR(buttonKey, new Boolean[]{true, false}, value -> { + objectBuilder.updateDefaultButton(buttonKey, "%" + placeholderRegex + "%", + value + ""); + objectBuilder.openInventory(); + return true; + }); + } + + /** + * A quick navigator for any type of array. + * Value cannot be empty, but it depends on the array + * if value can be null (be sure that all values are not null + * in case of not wanting this behaviour). + * By default, the value is the first element of the array. + * + * @param buttonKey The key of the button + * @param function The function to apply + * @return The button + */ + public static ObjectBuilderButton NAVIGATOR(String buttonKey, + T[] array, + Function function) { + ArrayNavigator navigator = new ArrayNavigator<>(array); + ObjectBuilderButton objectBuilderButton = new ObjectBuilderButton<>(buttonKey, + Optional.of(navigator.current()), + (button, player) -> { + T next = navigator.next(); + button.set(next); + }, function) { + }; + T current = navigator.current(); + function.apply(current); + return objectBuilderButton; + } + + /** + * A quick navigator for any type of array. + * Value cannot be empty nor null. + * By default, the value is the first element of the array. + * + * @param buttonKey The key of the button + * @param enumClass The enum class + * @param The type of the enum + * @param objectBuilder The object builder + * @return The button + */ + public static > ObjectBuilderButton ENUM_NAVIGATOR(String buttonKey, + Class enumClass, + ObjectBuilder objectBuilder) { + String placeholderRegex = NamingConventions.toCamelCase(buttonKey); + T[] array = enumClass.getEnumConstants(); + return NAVIGATOR(buttonKey, array, value -> { + objectBuilder.updateDefaultButton(buttonKey, "%" + placeholderRegex + "%", + value == null ? "N/A" : value.name()); + objectBuilder.openInventory(); + return true; + }); + } + + /** + * Manages a BlobEditor + * + * @param buttonKey The key of the button + * @param blobEditor The blob editor + * @param objectBuilder The object builder + * @param The type of the blob editor + * @return The button + */ + public static ObjectBuilderButton BLOB_EDITOR(String buttonKey, + BlobEditor blobEditor, + ObjectBuilder objectBuilder) { + + + String placeholderRegex = NamingConventions.toCamelCase(buttonKey); + return new ObjectBuilderButton<>(buttonKey, + Optional.empty(), + (button, player) -> { + api.addEditorListener(player, button::set, "Builder." + buttonKey, blobEditor); + }, t -> true) { + }; + } +} \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/entities/inventory/PlayerInventoryBuilder.java b/src/main/java/us/mytheria/bloblib/entities/inventory/PlayerInventoryBuilder.java new file mode 100644 index 00000000..2c08a677 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/inventory/PlayerInventoryBuilder.java @@ -0,0 +1,214 @@ +package us.mytheria.bloblib.entities.inventory; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Objects; +import java.util.UUID; + +/** + * @author anjoismysign + *

+ * An InventoryHolderBuilder is an instance of a InventoryBuilder. + * It's a wrapper for InventoryHolder's using the BlobLib API. + */ +public class PlayerInventoryBuilder extends InventoryBuilder { + private final UUID holderId; + private HashMap defaultButtons; + + /** + * Constructs a InventoryHolderBuilder with a title, size, and a ButtonManager. + * + * @param title The title of the inventory. + * @param size The size of the inventory. + * @param buttonManager The ButtonManager that will be used to manage the buttons. + */ + protected PlayerInventoryBuilder(@NotNull String title, int size, + @NotNull ButtonManager buttonManager, + @Nullable UUID holderId) { + Player player = Bukkit.getPlayer(holderId); + if (player == null) + throw new IllegalArgumentException("Player with UUID " + holderId + " is not online!"); + this.holderId = holderId; + this.setTitle(Objects.requireNonNull(title, + "'title' cannot be null!")); + this.setSize(size); + this.setButtonManager(Objects.requireNonNull(buttonManager, + "'buttonManager' cannot be null!")); + this.buildInventory(); + this.loadDefaultButtons(); + } + + /** + * Creates a new InventoryHolderBuilder in an empty constructor. + *

+ * I recommend you try constructing your InventoryHolderBuilder + * and get used to the API for a strong workflow. + * + * @param playerId The inventory holder + */ + public PlayerInventoryBuilder(UUID playerId) { + this.holderId = playerId; + } + + /** + * Copies itself to a new reference. + * + * @return The cloned inventory + */ + @NotNull + public PlayerInventoryBuilder copy() { + return new PlayerInventoryBuilder<>(getTitle(), getSize(), getButtonManager(), getHolderId()); + } + + @Nullable + public Player getPlayer() { + return Bukkit.getPlayer(holderId); + } + + /** + * Will create a new InventoryHolderBuilder with the same + * title, size, and ButtonManager with provided holder. + * + * @param holderId The inventory holder + * @return The new InventoryHolderBuilder + */ + @NotNull + public PlayerInventoryBuilder setHolderId(@NotNull UUID holderId) { + Objects.requireNonNull(holderId, "'holder' cannot be null!"); + return new PlayerInventoryBuilder<>(getTitle(), getSize(), getButtonManager(), holderId); + } + + /** + * Adds a default button using the specified key. + * Default buttons are meant to persist inventory + * changes. + * + * @param key The key of the button + */ + public void addDefaultButton(String key) { + for (Integer i : getSlots(key)) { + addDefaultButton(key, getButton(i)); + break; + } + } + + /** + * Loads/reloads the default buttons. + */ + public void loadDefaultButtons() { + setDefaultButtons(new HashMap<>()); + ButtonManager manager = getButtonManager(); + getKeys().forEach(this::addDefaultButton); + } + + /** + * Adds a default button to the memory of the inventory. + * + * @param name The name of the button + * @param item The ItemStack of the button + */ + public void addDefaultButton(String name, ItemStack item) { + defaultButtons.put(name, item); + } + + /** + * Searches for the ItemStack that is linked to said key. + * + * @param key The key of the button + * @return The ItemStack + */ + @Nullable + public ItemStack getDefaultButton(String key) { + return defaultButtons.get(key); + } + + /** + * Searches for the ItemStack that is linked to said key. + * The ItemStack is cloned and then returned. + * + * @param key The key of the button + * @return The cloned ItemStack + */ + @Nullable + public ItemStack cloneDefaultButton(String key) { + return defaultButtons.get(key).clone(); + } + + /** + * Retrieves the default buttons + * + * @return The default buttons + */ + @NotNull + public HashMap getDefaultButtons() { + return defaultButtons; + } + + /** + * Sets the default buttons + * + * @param defaultButtons The default buttons to set + */ + public void setDefaultButtons(HashMap defaultButtons) { + this.defaultButtons = defaultButtons; + } + + /** + * Builds the inventory since by default its reference is null + */ + public void buildInventory() { + if (getInventory() == null) + throw new IllegalStateException("PlayerInventory is null!"); + getButtonManager().getIntegerKeys().forEach(this::setButton); + } + + /** + * Retrieves the inventory + * + * @return The inventory if holder is not null, otherwise null. + */ + @Nullable + public Inventory getInventory() { + Player holder = getPlayer(); + if (holder == null) + return null; + return holder.getInventory(); + } + + /** + * Retrieves the inventory holder unique id + * + * @return The UUID + */ + @Nullable + public UUID getHolderId() { + return holderId; + } + + /** + * Sets the button at the specified slot + * + * @param slot The slot to set the button at + * @param itemStack The ItemStack to set the button to + */ + public void setButton(int slot, ItemStack itemStack) { + getInventory().setItem(slot, itemStack); + } + + /** + * Refills all ItemStacks that belong to said key + * + * @param key The key of the button + */ + public void refillButton(String key) { + for (Integer i : getSlots(key)) { + setButton(i, getButton(i)); + } + } +} \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/entities/inventory/ReferenceBlobInventory.java b/src/main/java/us/mytheria/bloblib/entities/inventory/ReferenceBlobInventory.java new file mode 100644 index 00000000..451beff4 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/inventory/ReferenceBlobInventory.java @@ -0,0 +1,34 @@ +package us.mytheria.bloblib.entities.inventory; + +import org.jetbrains.annotations.NotNull; + +/** + * Holds the reference of the key that points to the inventory. + * This key is tracked by entities such as BlobLib's InventoryManager. + */ +public class ReferenceBlobInventory extends BlobInventory { + private final String key; + + public static ReferenceBlobInventory of(InventoryBuilderCarrier carrier) { + return new ReferenceBlobInventory(carrier.title(), carrier.size(), + carrier.buttonManager(), carrier.reference()); + } + + public ReferenceBlobInventory(@NotNull String title, int size, + @NotNull ButtonManager buttonManager, + @NotNull String key) { + super(title, size, buttonManager); + this.key = key; + } + + @NotNull + public String getKey() { + return key; + } + + @Override + @NotNull + public ReferenceBlobInventory copy() { + return new ReferenceBlobInventory(getTitle(), getSize(), getButtonManager(), getKey()); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/inventory/ReferenceMetaBlobInventory.java b/src/main/java/us/mytheria/bloblib/entities/inventory/ReferenceMetaBlobInventory.java new file mode 100644 index 00000000..3e2737b1 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/inventory/ReferenceMetaBlobInventory.java @@ -0,0 +1,37 @@ +package us.mytheria.bloblib.entities.inventory; + +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +/** + * Holds the reference of the key that points to the inventory. + * This key is tracked by entities such as BlobLib's InventoryManager. + */ +public class ReferenceMetaBlobInventory extends MetaBlobInventory { + private final String key; + + public static ReferenceMetaBlobInventory of(InventoryBuilderCarrier carrier) { + return new ReferenceMetaBlobInventory(carrier.title(), carrier.size(), + carrier.buttonManager(), carrier.type(), carrier.reference()); + } + + public ReferenceMetaBlobInventory(@NotNull String title, int size, + @NotNull ButtonManager buttonManager, + @NotNull String type, + @NotNull String key) { + super(title, size, buttonManager, type); + this.key = Objects.requireNonNull(key, "'key' cannot be null!"); + } + + @NotNull + public String getKey() { + return key; + } + + @Override + @NotNull + public ReferenceMetaBlobInventory copy() { + return new ReferenceMetaBlobInventory(getTitle(), getSize(), getButtonManager(), getType(), getKey()); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/inventory/SharableInventory.java b/src/main/java/us/mytheria/bloblib/entities/inventory/SharableInventory.java new file mode 100644 index 00000000..233e9707 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/inventory/SharableInventory.java @@ -0,0 +1,191 @@ +package us.mytheria.bloblib.entities.inventory; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Objects; +import java.util.Set; + +/** + * @author anjoismysign + *

+ * A SharableInventory is an instance of a InventoryBuilder. + * It's my own version/way for managing GUI's / inventories + * using the Bukkit API. + */ +public class SharableInventory extends InventoryBuilder { + private Inventory inventory; + private HashMap defaultButtons; + + /** + * Constructs a SharableInventory with a title, size, and a ButtonManager. + * + * @param title The title of the inventory. + * @param size The size of the inventory. + * @param buttonManager The ButtonManager that will be used to manage the buttons. + */ + protected SharableInventory(@NotNull String title, int size, + @NotNull ButtonManager buttonManager) { + this.setTitle(Objects.requireNonNull(title, + "'title' cannot be null!")); + this.setSize(size); + this.setButtonManager(Objects.requireNonNull(buttonManager, + "'buttonManager' cannot be null!")); + this.buildInventory(); + this.loadDefaultButtons(); + } + + /** + * Creates a new SharableInventory in an empty constructor. + *

+ * I recommend you try constructing your SharableInventory + * and get used to the API for a strong workflow. + */ + public SharableInventory() { + } + + /** + * Copies itself to a new reference. + * + * @return The cloned inventory + */ + @NotNull + public SharableInventory copy() { + return new SharableInventory<>(getTitle(), getSize(), getButtonManager().copy()); + } + + /** + * Adds a default button using the specified key. + * Default buttons are meant to persist inventory + * changes. + * + * @param key The key of the button + */ + public void addDefaultButton(String key) { + for (Integer i : getSlots(key)) { + addDefaultButton(key, getButton(i)); + break; + } + } + + /** + * Loads/reloads the default buttons. + */ + public void loadDefaultButtons() { + setDefaultButtons(new HashMap<>()); + ButtonManager manager = getButtonManager(); + getKeys().forEach(this::addDefaultButton); + } + + /** + * Adds a default button to the memory of the inventory. + * + * @param name The name of the button + * @param item The ItemStack of the button + */ + public void addDefaultButton(String name, ItemStack item) { + defaultButtons.put(name, item); + } + + /** + * Searches for the ItemStack that is linked to said key. + * + * @param key The key of the button + * @return The ItemStack + */ + @Nullable + public ItemStack getDefaultButton(String key) { + return defaultButtons.get(key); + } + + /** + * Searches for the ItemStack that is linked to said key. + * The ItemStack is cloned and then returned. + * + * @param key The key of the button + * @return The cloned ItemStack + */ + @Nullable + public ItemStack cloneDefaultButton(String key) { + return defaultButtons.get(key).clone(); + } + + /** + * Retrieves the default buttons + * + * @return The default buttons + */ + @NotNull + public HashMap getDefaultButtons() { + return defaultButtons; + } + + /** + * Sets the default buttons + * + * @param defaultButtons The default buttons to set + */ + public void setDefaultButtons(HashMap defaultButtons) { + this.defaultButtons = defaultButtons; + } + + /** + * Builds the inventory since by default its reference is null + */ + public void buildInventory() { + inventory = Bukkit.createInventory(null, getSize(), getTitle()); + getButtonManager().getIntegerKeys().forEach(this::setButton); + } + + /** + * Retrieves the inventory + * + * @return The inventory + */ + @NotNull + public Inventory getInventory() { + return inventory; + } + + /** + * Sets the inventory + * + * @param inventory The inventory to set + */ + public void setInventory(Inventory inventory) { + this.inventory = inventory; + } + + /** + * Sets the button at the specified slot + * + * @param slot The slot to set the button at + * @param itemStack The ItemStack to set the button to + */ + public void setButton(int slot, ItemStack itemStack) { + inventory.setItem(slot, itemStack); + } + + /** + * Refills all ItemStacks that belong to said key + * + * @param key The key of the button + */ + public void refillButton(String key) { + Set set = getSlots(key); + if (set == null) + return; + for (Integer i : set) { + setButton(i, getButton(i)); + } + } + + public void open(Player player) { + player.openInventory(inventory); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/inventory/SuperBlobButtonManager.java b/src/main/java/us/mytheria/bloblib/entities/inventory/SuperBlobButtonManager.java deleted file mode 100644 index 6c18f046..00000000 --- a/src/main/java/us/mytheria/bloblib/entities/inventory/SuperBlobButtonManager.java +++ /dev/null @@ -1,126 +0,0 @@ -package us.mytheria.bloblib.entities.inventory; - -import me.anjoismysign.anjo.entities.Uber; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.inventory.ItemStack; -import us.mytheria.bloblib.entities.CommandMultiSlotable; -import us.mytheria.bloblib.entities.SuperInventoryButton; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Set; - -public class SuperBlobButtonManager extends ButtonManager { - private HashMap buttonCommands; - - public static SuperBlobButtonManager smartFromConfigurationSection(ConfigurationSection section) { - SuperBlobButtonManager superBlobButtonManager = new SuperBlobButtonManager(); - superBlobButtonManager.read(section); - return superBlobButtonManager; - } - - /** - * Builds a SuperButtonManager through the specified ConfigurationSection. - * Uses HashMap to store buttons. - * - * @param section configuration section which contains all the buttons - * @return a non abstract ButtonManager. - */ - public static SuperBlobButtonManager fromConfigurationSection(ConfigurationSection section) { - SuperBlobButtonManager blobButtonManager = new SuperBlobButtonManager(); - blobButtonManager.add(section); - return blobButtonManager; - } - - /** - * Builds a non abstract ButtonManager without any buttons stored yet. - */ - public SuperBlobButtonManager() { - super(new HashMap<>(), new HashMap<>()); - buttonCommands = new HashMap<>(); - } - - @Override - public boolean contains(String key) { - return getStringKeys().containsKey(key); - } - - @Override - public boolean contains(int slot) { - return getIntegerKeys().containsKey(slot); - } - - @Override - public Set get(String key) { - return getStringKeys().get(key); - } - - @Override - public Collection keys() { - return getStringKeys().keySet(); - } - - @Override - public ItemStack get(int slot) { - return getIntegerKeys().get(slot); - } - - @Override - public Collection buttons() { - return getIntegerKeys().values(); - } - - /** - * adds all buttons inside a configuration section - * - * @param section configuration section which contains all the buttons - * @return true if at least one button was succesfully added. - * this is determined in case the being called after the first add call - */ - @Override - public boolean add(ConfigurationSection section) { - Set set = section.getKeys(false); - Uber madeChanges = new Uber<>(false); - set.stream().filter(s -> !contains(s)).forEach(s -> { - madeChanges.talk(true); - CommandMultiSlotable slotable = CommandMultiSlotable.fromConfigurationSection(section.getConfigurationSection(s), s); - slotable.setInSuperBlobButtonManager(this); - }); - return madeChanges.thanks(); - } - - public boolean read(ConfigurationSection section) { - Set set = section.getKeys(false); - Uber madeChanges = new Uber<>(false); - set.stream().filter(s -> !contains(s)).forEach(s -> { - madeChanges.talk(true); - CommandMultiSlotable slotable = CommandMultiSlotable.fromConfigurationSection(section.getConfigurationSection(s), s); - slotable.setInSuperBlobButtonManager(this); - }); - set.forEach(s -> { - if (contains(s)) - return; - madeChanges.talk(true); - CommandMultiSlotable slotable = CommandMultiSlotable.read(section.getConfigurationSection(s), s); - slotable.setInSuperBlobButtonManager(this); - }); - return madeChanges.thanks(); - } - - @Override - public void addCommand(String key, String command) { - buttonCommands.put(key, command); - } - - @Override - public String getCommand(String key) { - return buttonCommands.get(key); - } - - @Override - public SuperInventoryButton getSuperButton(String key) { - String command = getCommand(key); - return SuperInventoryButton.fromInventoryButton(getButton(key), command, - command != null); - } -} diff --git a/src/main/java/us/mytheria/bloblib/entities/inventory/SuperBlobInventory.java b/src/main/java/us/mytheria/bloblib/entities/inventory/SuperBlobInventory.java deleted file mode 100644 index 713b6043..00000000 --- a/src/main/java/us/mytheria/bloblib/entities/inventory/SuperBlobInventory.java +++ /dev/null @@ -1,110 +0,0 @@ -package us.mytheria.bloblib.entities.inventory; - -import org.bukkit.Bukkit; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; -import us.mytheria.bloblib.utilities.TextColor; - -import java.io.File; - -public class SuperBlobInventory extends BlobInventory { - - public static SuperBlobInventory smartFromFile(File file) { - YamlConfiguration configuration = YamlConfiguration.loadConfiguration(file); - String title = TextColor.PARSE(configuration.getString("Title", configuration.getName() + ">NOT-SET")); - int size = configuration.getInt("Size", -1); - if (size < 0 || size % 9 != 0) { - if (size < 0) { - size = 54; - Bukkit.getLogger().info(configuration.getName() + "'s Size is smaller than 0."); - Bukkit.getLogger().info("This was probably due because you never set a Size."); - Bukkit.getLogger().info("This is not possible in an inventory so it was set"); - Bukkit.getLogger().info("to '54' which is default."); - } else { - size = 54; - Bukkit.getLogger().info(configuration.getName() + "'s Size is not a factor of 9."); - Bukkit.getLogger().info("This is not possible in an inventory so it was set"); - Bukkit.getLogger().info("to '54' which is default."); - } - } - SuperBlobButtonManager buttonManager = SuperBlobButtonManager.smartFromConfigurationSection(configuration.getConfigurationSection("Buttons")); - SuperBlobInventory inventory = new SuperBlobInventory(title, size, buttonManager); - return inventory; - } - - public static SuperBlobInventory fromFile(File file) { - YamlConfiguration configuration = YamlConfiguration.loadConfiguration(file); - String title = TextColor.PARSE(configuration.getString("Title", configuration.getName() + ">NOT-SET")); - int size = configuration.getInt("Size", -1); - if (size < 0 || size % 9 != 0) { - if (size < 0) { - size = 54; - Bukkit.getLogger().info(configuration.getName() + "'s Size is smaller than 0."); - Bukkit.getLogger().info("This was probably due because you never set a Size."); - Bukkit.getLogger().info("This is not possible in an inventory so it was set"); - Bukkit.getLogger().info("to '54' which is default."); - } else { - size = 54; - Bukkit.getLogger().info(configuration.getName() + "'s Size is not a factor of 9."); - Bukkit.getLogger().info("This is not possible in an inventory so it was set"); - Bukkit.getLogger().info("to '54' which is default."); - } - } - SuperBlobButtonManager buttonManager = SuperBlobButtonManager.fromConfigurationSection(configuration.getConfigurationSection("Buttons")); - SuperBlobInventory inventory = new SuperBlobInventory(title, size, buttonManager); - return inventory; - } - - public static SuperBlobInventory smarFromConfigurationSection(ConfigurationSection section) { - String title = TextColor.PARSE(section.getString("Title", section.getName() + ">NOT-SET")); - int size = section.getInt("Size", -1); - if (size < 0 || size % 9 != 0) { - if (size < 0) { - size = 54; - Bukkit.getLogger().info(section.getName() + "'s Size is smaller than 0."); - Bukkit.getLogger().info("This was probably due because you never set a Size."); - Bukkit.getLogger().info("This is not possible in an inventory so it was set"); - Bukkit.getLogger().info("to '54' which is default."); - } else { - size = 54; - Bukkit.getLogger().info(section.getName() + "'s Size is not a factor of 9."); - Bukkit.getLogger().info("This is not possible in an inventory so it was set"); - Bukkit.getLogger().info("to '54' which is default."); - } - } - SuperBlobButtonManager buttonManager = SuperBlobButtonManager.smartFromConfigurationSection(section.getConfigurationSection("Buttons")); - SuperBlobInventory inventory = new SuperBlobInventory(title, size, buttonManager); - return inventory; - } - - public static SuperBlobInventory fromConfigurationSection(ConfigurationSection section) { - String title = TextColor.PARSE(section.getString("Title", section.getName() + ">NOT-SET")); - int size = section.getInt("Size", -1); - if (size < 0 || size % 9 != 0) { - if (size < 0) { - size = 54; - Bukkit.getLogger().info(section.getName() + "'s Size is smaller than 0."); - Bukkit.getLogger().info("This was probably due because you never set a Size."); - Bukkit.getLogger().info("This is not possible in an inventory so it was set"); - Bukkit.getLogger().info("to '54' which is default."); - } else { - size = 54; - Bukkit.getLogger().info(section.getName() + "'s Size is not a factor of 9."); - Bukkit.getLogger().info("This is not possible in an inventory so it was set"); - Bukkit.getLogger().info("to '54' which is default."); - } - } - SuperBlobButtonManager buttonManager = SuperBlobButtonManager.fromConfigurationSection(section.getConfigurationSection("Buttons")); - SuperBlobInventory inventory = new SuperBlobInventory(title, size, buttonManager); - return inventory; - } - - private SuperBlobInventory(String title, int size, ButtonManager buttonManager) { - super(title, size, buttonManager); - } - - @Override - public SuperBlobButtonManager getButtonManager() { - return (SuperBlobButtonManager) super.getButtonManager(); - } -} diff --git a/src/main/java/us/mytheria/bloblib/entities/inventory/SuperFurnace.java b/src/main/java/us/mytheria/bloblib/entities/inventory/SuperFurnace.java new file mode 100644 index 00000000..7a4826dd --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/inventory/SuperFurnace.java @@ -0,0 +1,222 @@ +package us.mytheria.bloblib.entities.inventory; + +import me.anjoismysign.anjo.entities.Result; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import us.mytheria.bloblib.Experience; +import us.mytheria.bloblib.FuelAPI; +import us.mytheria.bloblib.entities.Fuel; +import us.mytheria.bloblib.entities.FurnaceOperation; + +import java.util.NoSuchElementException; +import java.util.Objects; + +/** + * @author anjoismysign + *

+ * A SuperFurnace is an instance of a SharableInventory. + * It's a custom Furnace inventory that automatically smelts items. + * Each operation will have a fuel size. + * In order to make an operation, see {@link #handle()}. + */ +public class SuperFurnace extends SharableInventory { + private T fuelButton; + private T inputButton; + private long operationSize = 200; + private long storage = 0; + private int outputSlot; + private FurnaceOperation lastOperation; + private final FuelAPI fuelAPI; + + public static SuperFurnace + fromInventoryBuilderCarrier(InventoryBuilderCarrier carrier, + long operationSize) { + ButtonManager buttonManager = carrier.buttonManager(); + T fuelButton = buttonManager.getButton("Fuel"); + if (fuelButton == null) + return null; + if (fuelButton.getSlots().size() != 1) + return null; + + T inputButton = buttonManager.getButton("Input"); + if (inputButton == null) + return null; + if (inputButton.getSlots().size() != 1) + return null; + + T outputButton = buttonManager.getButton("Output"); + if (outputButton == null) + return null; + if (outputButton.getSlots().size() != 1) + return null; + int outputSlot = outputButton.getSlots().stream().findFirst() + .orElseThrow(() -> new NoSuchElementException( + "Inventory '" + carrier.title() + "' has an empty " + + "Output button!")); + return new SuperFurnace<>(carrier.title(), carrier.size(), + buttonManager, operationSize, outputSlot, + fuelButton, inputButton); + } + + /** + * Constructs a InventoryHolderBuilder with a title, size, and a ButtonManager. + * + * @param title The title of the inventory. + * @param size The size of the inventory. + * @param buttonManager The ButtonManager that will be used to manage the buttons. + */ + protected SuperFurnace(@NotNull String title, int size, + @NotNull ButtonManager buttonManager, + long operationSize, + int outputSlot, + @Nullable T fuelButton, + @Nullable T inputButton) { + this.setTitle(Objects.requireNonNull(title, + "'title' cannot be null!")); + this.setSize(size); + this.setButtonManager(Objects.requireNonNull(buttonManager, + "'buttonManager' cannot be null!")); + this.buildInventory(); + this.loadDefaultButtons(); + this.operationSize = operationSize; + this.outputSlot = outputSlot; + this.fuelButton = fuelButton; + this.inputButton = inputButton; + this.fuelAPI = FuelAPI.getInstance(); + } + + /** + * Creates a new InventoryHolderBuilder in an empty constructor. + * Default operation size is 200. + *

+ * I recommend you try constructing your InventoryHolderBuilder + * and get used to the API for a strong workflow. + */ + public SuperFurnace() { + this.fuelAPI = FuelAPI.getInstance(); + } + + /** + * Copies itself to a new reference. + * + * @return The cloned inventory + */ + @NotNull + public SuperFurnace copy() { + return new SuperFurnace<>(getTitle(), getSize(), + getButtonManager().copy(), getOperationSize(), + getOutputSlot(), getFuelButton(), getInputButton()); + } + + /** + * Will return the size of fuel required for each operation. + * + * @return The size of fuel required for each operation. + */ + public long getOperationSize() { + return operationSize; + } + + /** + * Will return the fuel stored in the current super furnace. + * + * @return The fuel stored in the current super furnace. + */ + public long getStorage() { + return storage; + } + + /** + * Will attempt to smelt the input item. + * + * @param fuel The fuel to use. + * @param input The input item to smelt. + * @return The result of the operation. + */ + public FurnaceOperation smelt(ItemStack fuel, ItemStack input) { + int amount = input.getAmount(); + long operationSize = this.operationSize * amount; + int compare = Long.compare(storage, operationSize); + if (compare <= 0) { + Result isFuel = fuelAPI.isFuel(fuel.getType()); + if (!isFuel.isValid()) + return FurnaceOperation.fail(); + Fuel value = isFuel.value(); + long ticks = value.getBurnTime() * amount; + storage += ticks; + } + compare = Long.compare(storage, operationSize); + if (compare == -1) + return FurnaceOperation.fail(); + return FurnaceOperation.vanilla(input); + } + + /** + * @return The fuel ItemStack. Throws NoSuchElementException if the fuel button is empty. + */ + public ItemStack getFuel() { + return getButton(fuelButton.getSlots().stream().findFirst().orElseThrow()); + } + + /** + * @return The input ItemStack. Throws NoSuchElementException if the input button is empty. + */ + public ItemStack getInput() { + return getButton(inputButton.getSlots().stream().findFirst().orElseThrow()); + } + + public int getOutputSlot() { + return this.outputSlot; + } + + /** + * Will attempt to smelt the input item with the fuel. + * + * @return True if the operation was successful. False otherwise. + */ + public boolean handle() { + int outputSlot = getOutputSlot(); + if (lastOperation != null) + return false; + ItemStack fuel = getFuel(); + ItemStack input = getInput(); + + FurnaceOperation operation = smelt(fuel, input); + if (!operation.success()) + return false; + ItemStack output = operation.result().clone(); + setButton(outputSlot, output); + lastOperation = operation; + return true; + } + + /** + * Will apply the experience to the player and remove the last operation. + * + * @param player The player to apply the experience to. + */ + public void apply(Player player) { + if (lastOperation == null) + return; + float experience = lastOperation.experience(); + Experience.changeExp(player, experience); + lastOperation = null; + } + + @Nullable + public FurnaceOperation getLastOperation() { + return this.lastOperation; + } + + @NotNull + public T getFuelButton() { + return this.fuelButton; + } + + @NotNull + public T getInputButton() { + return this.inputButton; + } +} \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/entities/inventory/VariableSelector.java b/src/main/java/us/mytheria/bloblib/entities/inventory/VariableSelector.java index 46f8a121..55c5098d 100644 --- a/src/main/java/us/mytheria/bloblib/entities/inventory/VariableSelector.java +++ b/src/main/java/us/mytheria/bloblib/entities/inventory/VariableSelector.java @@ -1,21 +1,15 @@ package us.mytheria.bloblib.entities.inventory; import org.bukkit.Bukkit; -import org.bukkit.Sound; -import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; -import us.mytheria.bloblib.BlobLib; -import us.mytheria.bloblib.BlobLibAssetAPI; +import us.mytheria.bloblib.api.BlobLibInventoryAPI; +import us.mytheria.bloblib.api.BlobLibSoundAPI; import us.mytheria.bloblib.entities.VariableFiller; import us.mytheria.bloblib.entities.VariableValue; -import us.mytheria.bloblib.managers.BlobLibFileManager; import javax.annotation.Nullable; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.UUID; +import java.util.*; import java.util.function.Function; /** @@ -33,7 +27,7 @@ public abstract class VariableSelector extends BlobInventory { private final UUID builderId; private final VariableFiller filler; private int page; - private final int itemsPerPage; + private int itemsPerPage; /** * Creates a new VariableSelector @@ -41,20 +35,7 @@ public abstract class VariableSelector extends BlobInventory { * @return the new VariableSelector */ public static BlobInventory DEFAULT() { - return BlobLibAssetAPI.getBlobInventory("VariableSelector"); - } - - /** - * Creates a new VariableSelector - * - * @return the new VariableSelector - * @deprecated use {@link #DEFAULT()} instead - */ - @Deprecated - public static BlobInventory DEFAULT_ITEMSTACKREADER() { - BlobLibFileManager fileManager = BlobLib.getInstance().getFileManager(); - YamlConfiguration inventories = fileManager.getYml(fileManager.defaultInventoriesFile()); - return smartFromConfigurationSection(inventories.getConfigurationSection("VariableSelector")); + return BlobLibInventoryAPI.getInstance().getBlobInventory("VariableSelector"); } /** @@ -71,11 +52,15 @@ public VariableSelector(BlobInventory blobInventory, UUID builderId, this.filler = filler; this.builderId = builderId; this.values = new HashMap<>(); - this.dataType = dataType; - setTitle(blobInventory.getTitle().replace("%variable%", dataType)); + this.dataType = dataType.toUpperCase(); + if (dataType != null) + setTitle(blobInventory.getTitle().replace("%variable%", dataType)); buildInventory(); this.page = 1; - this.itemsPerPage = getSlots("White-Background").size(); + this.itemsPerPage = 1; + Set slots = getSlots("White-Background"); + if (slots != null) + setItemsPerPage(slots.size()); loadInConstructor(); } @@ -287,7 +272,7 @@ public void setPage(int page) { public void nextPage() { setPage(page + 1); Player player = getPlayer(); - player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 1, 1); + BlobLibSoundAPI.getInstance().getSound("Builder.Button-Click").handle(player); } /** @@ -296,7 +281,7 @@ public void nextPage() { public void previousPage() { setPage(page - 1); Player player = getPlayer(); - player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 1, 1); + BlobLibSoundAPI.getInstance().getSound("Builder.Button-Click").handle(player); } /** @@ -328,4 +313,8 @@ public void addValues(Collection collection, boolean noDuplicates) { public void addValues(Collection collection) { addValues(collection, false); } + + public void setItemsPerPage(int itemsPerPage) { + this.itemsPerPage = itemsPerPage; + } } diff --git a/src/main/java/us/mytheria/bloblib/entities/listeners/BlobChatListener.java b/src/main/java/us/mytheria/bloblib/entities/listeners/BlobChatListener.java index 5202368e..0a1f0bd2 100644 --- a/src/main/java/us/mytheria/bloblib/entities/listeners/BlobChatListener.java +++ b/src/main/java/us/mytheria/bloblib/entities/listeners/BlobChatListener.java @@ -5,10 +5,11 @@ import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitTask; import us.mytheria.bloblib.BlobLib; -import us.mytheria.bloblib.BlobLibAssetAPI; +import us.mytheria.bloblib.api.BlobLibMessageAPI; import us.mytheria.bloblib.entities.message.BlobMessage; import us.mytheria.bloblib.managers.ChatListenerManager; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -57,9 +58,13 @@ public static BlobChatListener smart(Player owner, long timeout, Consumer timeoutMessage = Optional.ofNullable(BlobLibAssetAPI.getMessage(timeoutMessageKey)); - Optional timerMessage = Optional.ofNullable(BlobLibAssetAPI.getMessage(timerMessageKey)); + Optional timeoutMessage = Optional.ofNullable(BlobLibMessageAPI.getInstance().getMessage(timeoutMessageKey)); + Optional timerMessage = Optional.ofNullable(BlobLibMessageAPI.getInstance().getMessage(timerMessageKey)); List messages = timerMessage.map(Collections::singletonList).orElse(Collections.emptyList()); + List timerMessages = new ArrayList<>(); + messages.forEach(message -> timerMessages.add( + message.modify(s -> s.replace("%world%", owner.getWorld().getName())) + )); return new BlobChatListener(owner.getName(), timeout, inputListener -> { String input = inputListener.getInput(); @@ -73,8 +78,8 @@ public static BlobChatListener smart(Player owner, long timeout, Consumer { chatManager.removeChatListener(owner); - timeoutMessage.ifPresent(blobMessage -> blobMessage.sendAndPlay(owner)); - }, messages); + timeoutMessage.ifPresent(blobMessage -> blobMessage.handle(owner)); + }, timerMessages); } /** @@ -101,15 +106,15 @@ private BlobChatListener(String owner, long timeout, Consumer @Override public void runTasks() { super.runTasks(); + Player player = Bukkit.getPlayer(getOwner()); BukkitRunnable bukkitRunnable = new BukkitRunnable() { @Override public void run() { - Player player = Bukkit.getPlayer(getOwner()); if (player == null || !player.isOnline()) { this.cancel(); return; } - messages.forEach(message -> message.sendAndPlay(player)); + messages.forEach(message -> message.handle(player)); } }; this.messageTask = bukkitRunnable.runTaskTimerAsynchronously(BlobLib.getInstance(), 0, 10); diff --git a/src/main/java/us/mytheria/bloblib/entities/listeners/BlobDropListener.java b/src/main/java/us/mytheria/bloblib/entities/listeners/BlobDropListener.java index 147387c0..6ddca64b 100644 --- a/src/main/java/us/mytheria/bloblib/entities/listeners/BlobDropListener.java +++ b/src/main/java/us/mytheria/bloblib/entities/listeners/BlobDropListener.java @@ -6,7 +6,7 @@ import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitTask; import us.mytheria.bloblib.BlobLib; -import us.mytheria.bloblib.BlobLibAssetAPI; +import us.mytheria.bloblib.api.BlobLibMessageAPI; import us.mytheria.bloblib.entities.message.BlobMessage; import us.mytheria.bloblib.managers.DropListenerManager; @@ -50,7 +50,7 @@ public static BlobDropListener smart(Player owner, Consumer consumer, String timerMessageKey) { BlobLib main = BlobLib.getInstance(); DropListenerManager dropManager = main.getDropListenerManager(); - Optional timerMessage = Optional.ofNullable(BlobLibAssetAPI.getMessage(timerMessageKey)); + Optional timerMessage = Optional.ofNullable(BlobLibMessageAPI.getInstance().getMessage(timerMessageKey)); List messages = timerMessage.map(Collections::singletonList).orElse(Collections.emptyList()); return new BlobDropListener(owner.getName(), listener -> { ItemStack input = listener.getInput(); @@ -83,15 +83,15 @@ private BlobDropListener(String owner, Consumer inputConsumer, @Override public void runTasks() { super.runTasks(); + Player player = Bukkit.getPlayer(getOwner()); BukkitRunnable bukkitRunnable = new BukkitRunnable() { @Override public void run() { - Player player = Bukkit.getPlayer(getOwner()); if (player == null || !player.isOnline()) { this.cancel(); return; } - messages.forEach(message -> message.sendAndPlay(player)); + messages.forEach(message -> message.handle(player)); } }; this.messageTask = bukkitRunnable.runTaskTimerAsynchronously(BlobLib.getInstance(), 0, 10); diff --git a/src/main/java/us/mytheria/bloblib/entities/listeners/BlobEditorListener.java b/src/main/java/us/mytheria/bloblib/entities/listeners/BlobEditorListener.java new file mode 100644 index 00000000..d916990f --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/listeners/BlobEditorListener.java @@ -0,0 +1,111 @@ +package us.mytheria.bloblib.entities.listeners; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.scheduler.BukkitTask; +import us.mytheria.bloblib.BlobLib; +import us.mytheria.bloblib.api.BlobLibMessageAPI; +import us.mytheria.bloblib.entities.BlobEditor; +import us.mytheria.bloblib.entities.message.BlobMessage; +import us.mytheria.bloblib.managers.SelectorListenerManager; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +public class BlobEditorListener extends EditorListener { + private final List messages; + private BukkitTask messageTask; + + /** + * Will run a SelectorListener which will send messages to player every 10 ticks asynchronously. + * Will check if input is null. If so, will close player's inventory preventing + * dupe exploits and will also return, not running the consumer. + * Note that if not null, player's inventory won't be closed, so you need to make sure + * to close it if you need to, preferably in the consumer. + * + * @param player The player to send messages to + * @param consumer The consumer to run when the SelectorListener receives input + * @param timerMessageKey The key of the message to send to the player + * @param selector The selector to use + * @param The type of the input + * @return The SelectorListener + */ + public static BlobEditorListener wise(Player player, Consumer consumer, + String timerMessageKey, + BlobEditor selector) { + BlobLib main = BlobLib.getInstance(); + SelectorListenerManager selectorManager = main.getSelectorManager(); + Optional timerMessage = Optional.empty(); + if (timerMessageKey != null) + timerMessage = Optional.ofNullable(BlobLibMessageAPI.getInstance().getMessage(timerMessageKey)); + List messages = timerMessage.map(Collections::singletonList).orElse(new ArrayList<>()); + return new BlobEditorListener<>(player.getName(), listener -> { + T input = listener.getInput(); + selectorManager.removeEditorListener(player); + if (input == null) { + player.closeInventory(); + return; + } + Bukkit.getScheduler().runTask(main, () -> { + if (player == null || !player.isOnline()) { + return; + } + consumer.accept(input); + }); + }, messages, selector); + } + + /** + * Will run a SelectorListener which will send messages to player every 10 ticks asynchronously + * + * @param owner The player's name which is owner of the SelectorListener + * @param inputRunnable The runnable to run when the SelectorListener receives input + * @param messages The messages to send to the player + */ + @Deprecated + private BlobEditorListener(String owner, Runnable inputRunnable, List messages, + BlobEditor selector) { + super(owner, inputRunnable, selector); + this.messages = messages; + } + + private BlobEditorListener(String owner, Consumer> inputConsumer, + List messages, BlobEditor selector) { + super(owner, inputConsumer, selector); + this.messages = messages; + } + + @Override + public void runTasks() { + Player player = Bukkit.getPlayer(getOwner()); + BukkitRunnable bukkitRunnable = new BukkitRunnable() { + @Override + public void run() { + if (player == null || !player.isOnline()) { + this.cancel(); + return; + } + if (messages.isEmpty()) + return; + for (BlobMessage message : messages) { + if (message != null) + message.handle(player); + } + } + }; + this.messageTask = bukkitRunnable.runTaskTimerAsynchronously(BlobLib.getInstance(), 0, 10); + } + + @Override + public void cancel() { + messageTask.cancel(); + } + + public List getMessages() { + return messages; + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/listeners/BlobSelPosListener.java b/src/main/java/us/mytheria/bloblib/entities/listeners/BlobSelPosListener.java index dcc055f8..d1ac37bb 100644 --- a/src/main/java/us/mytheria/bloblib/entities/listeners/BlobSelPosListener.java +++ b/src/main/java/us/mytheria/bloblib/entities/listeners/BlobSelPosListener.java @@ -6,7 +6,7 @@ import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitTask; import us.mytheria.bloblib.BlobLib; -import us.mytheria.bloblib.BlobLibAssetAPI; +import us.mytheria.bloblib.api.BlobLibMessageAPI; import us.mytheria.bloblib.entities.message.BlobMessage; import us.mytheria.bloblib.managers.SelPosListenerManager; @@ -49,8 +49,8 @@ public static BlobSelPosListener smart(Player player, long timeout, Consumer timeoutMessage = Optional.ofNullable(BlobLibAssetAPI.getMessage(timeoutMessageKey)); - Optional timerMessage = Optional.ofNullable(BlobLibAssetAPI.getMessage(timerMessageKey)); + Optional timeoutMessage = Optional.ofNullable(BlobLibMessageAPI.getInstance().getMessage(timeoutMessageKey)); + Optional timerMessage = Optional.ofNullable(BlobLibMessageAPI.getInstance().getMessage(timerMessageKey)); List messages = timerMessage.map(Collections::singletonList).orElse(Collections.emptyList()); return new BlobSelPosListener(player.getName(), timeout, inputListener -> { @@ -81,15 +81,15 @@ private BlobSelPosListener(String owner, long timeout, Consumer message.sendAndPlay(player)); + messages.forEach(message -> message.handle(player)); } }; this.messageTask = bukkitRunnable.runTaskTimerAsynchronously(BlobLib.getInstance(), 0, 10); diff --git a/src/main/java/us/mytheria/bloblib/entities/listeners/BlobSelectorListener.java b/src/main/java/us/mytheria/bloblib/entities/listeners/BlobSelectorListener.java index 162ca7ba..e7d882ec 100644 --- a/src/main/java/us/mytheria/bloblib/entities/listeners/BlobSelectorListener.java +++ b/src/main/java/us/mytheria/bloblib/entities/listeners/BlobSelectorListener.java @@ -4,12 +4,14 @@ import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitTask; +import org.jetbrains.annotations.Nullable; import us.mytheria.bloblib.BlobLib; -import us.mytheria.bloblib.BlobLibAssetAPI; +import us.mytheria.bloblib.api.BlobLibMessageAPI; import us.mytheria.bloblib.entities.inventory.VariableSelector; import us.mytheria.bloblib.entities.message.BlobMessage; import us.mytheria.bloblib.managers.SelectorListenerManager; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -43,6 +45,7 @@ public static BlobSelectorListener build(Player owner, Runnable inputRunn * @param selector The selector to use * @param The type of the input * @return The SelectorListener + * @deprecated Use {@link #wise(Player, Consumer, String, VariableSelector)} instead. */ @Deprecated public static BlobSelectorListener smart(Player player, Consumer consumer, @@ -50,7 +53,7 @@ public static BlobSelectorListener smart(Player player, Consumer consu VariableSelector selector) { BlobLib main = BlobLib.getInstance(); SelectorListenerManager selectorManager = main.getSelectorManager(); - Optional timerMessage = Optional.ofNullable(BlobLibAssetAPI.getMessage(timerMessageKey)); + Optional timerMessage = Optional.ofNullable(BlobLibMessageAPI.getInstance().getMessage(timerMessageKey)); List messages = timerMessage.map(Collections::singletonList).orElse(Collections.emptyList()); return new BlobSelectorListener<>(player.getName(), () -> { @SuppressWarnings("unchecked") T input = (T) selectorManager.getInput(player); @@ -68,7 +71,7 @@ public static BlobSelectorListener smart(Player player, Consumer consu * Will run a SelectorListener which will send messages to player every 10 ticks asynchronously. * Will check if input is null. If so, will close player's inventory preventing * dupe exploits and will also return, not running the consumer. - * Note that if not null, player's inventory won't be closed so you need to make sure + * Note that if not null, player's inventory won't be closed, so you need to make sure * to close it if you need to, preferably in the consumer. * * @param player The player to send messages to @@ -79,14 +82,16 @@ public static BlobSelectorListener smart(Player player, Consumer consu * @return The SelectorListener */ public static BlobSelectorListener wise(Player player, Consumer consumer, - String timerMessageKey, + @Nullable String timerMessageKey, VariableSelector selector) { BlobLib main = BlobLib.getInstance(); SelectorListenerManager selectorManager = main.getSelectorManager(); - Optional timerMessage = Optional.ofNullable(BlobLibAssetAPI.getMessage(timerMessageKey)); - List messages = timerMessage.map(Collections::singletonList).orElse(Collections.emptyList()); - return new BlobSelectorListener<>(player.getName(), selectorListener -> { - T input = selectorListener.getInput(); + Optional timerMessage = Optional.empty(); + if (timerMessageKey != null) + timerMessage = Optional.ofNullable(BlobLibMessageAPI.getInstance().getMessage(timerMessageKey)); + List messages = timerMessage.map(Collections::singletonList).orElse(new ArrayList<>()); + return new BlobSelectorListener<>(player.getName(), listener -> { + T input = listener.getInput(); selectorManager.removeSelectorListener(player); if (input == null) { player.closeInventory(); @@ -123,15 +128,20 @@ private BlobSelectorListener(String owner, Consumer> inputCo @Override public void runTasks() { + Player player = Bukkit.getPlayer(getOwner()); BukkitRunnable bukkitRunnable = new BukkitRunnable() { @Override public void run() { - Player player = Bukkit.getPlayer(getOwner()); if (player == null || !player.isOnline()) { this.cancel(); return; } - messages.forEach(message -> message.sendAndPlay(player)); + if (messages.isEmpty()) + return; + for (BlobMessage message : messages) { + if (message != null) + message.handle(player); + } } }; this.messageTask = bukkitRunnable.runTaskTimerAsynchronously(BlobLib.getInstance(), 0, 10); diff --git a/src/main/java/us/mytheria/bloblib/entities/listeners/EditorActionType.java b/src/main/java/us/mytheria/bloblib/entities/listeners/EditorActionType.java new file mode 100644 index 00000000..ea253d6c --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/listeners/EditorActionType.java @@ -0,0 +1,9 @@ +package us.mytheria.bloblib.entities.listeners; + +public enum EditorActionType { + ADD, + REMOVE, + NEXT_PAGE, + PREVIOUS_PAGE, + NONE +} \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/entities/listeners/EditorListener.java b/src/main/java/us/mytheria/bloblib/entities/listeners/EditorListener.java new file mode 100644 index 00000000..c5106954 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/listeners/EditorListener.java @@ -0,0 +1,58 @@ +package us.mytheria.bloblib.entities.listeners; + +import us.mytheria.bloblib.BlobLib; +import us.mytheria.bloblib.entities.BlobEditor; +import us.mytheria.bloblib.entities.inventory.VariableSelector; + +import java.util.function.Consumer; + +public class EditorListener extends InputListener { + private T input; + private final BlobEditor editor; + + public EditorListener(String owner, Runnable inputRunnable, BlobEditor editor) { + super(owner, inputListener -> { + inputRunnable.run(); + }); + this.editor = editor; + register(); + editor.open(); + } + + @SuppressWarnings("unchecked") + public EditorListener(String owner, Consumer> inputConsumer, + BlobEditor editor) { + super(owner, inputListener -> { + inputConsumer.accept((EditorListener) inputListener); + }); + this.editor = editor; + register(); + editor.open(); + } + + private void register() { + BlobLib.getInstance().getVariableSelectorManager().addEditorSelector(editor); + } + + @Override + public T getInput() { + return input; + } + + public void setInput(T input) { + this.input = input; + cancel(); + inputConsumer.accept(this); + } + + @SuppressWarnings("unchecked") + public void setInputFromSlot(VariableSelector selector, int slot) { + if (!getEditor().equals(this.editor)) + return; + setInput((T) selector.getValue(slot)); + } + + public BlobEditor getEditor() { + return editor; + } +} \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/entities/logger/BlobPluginLogger.java b/src/main/java/us/mytheria/bloblib/entities/logger/BlobPluginLogger.java index 9a54a370..f693ed0c 100644 --- a/src/main/java/us/mytheria/bloblib/entities/logger/BlobPluginLogger.java +++ b/src/main/java/us/mytheria/bloblib/entities/logger/BlobPluginLogger.java @@ -23,12 +23,12 @@ public BlobPluginLogger(JavaPlugin plugin) { @Override public void log(String message) { - super.log(ChatColor.GREEN + prefix() + start() + message); + super.log(prefix() + start() + message); } @Override public void debug(String message) { - super.debug(ChatColor.GOLD + prefix() + start() + message); + super.debug(ChatColor.GREEN + prefix() + start() + message); } @Override @@ -36,13 +36,18 @@ public void error(String message) { super.error(ChatColor.RED + prefix() + start() + message); } + @Override + public void throwable(Throwable throwable) { + error(throwable.getMessage()); + } + @Override public void exception(Exception exception) { - error(exception.getMessage()); + throwable(exception); } public String prefix() { - return "<{" + plugin.getName() + "}>"; + return "{" + plugin.getName() + "}"; } public String start() { diff --git a/src/main/java/us/mytheria/bloblib/entities/logger/ConsoleLogger.java b/src/main/java/us/mytheria/bloblib/entities/logger/ConsoleLogger.java index 895fd249..a706ad1e 100644 --- a/src/main/java/us/mytheria/bloblib/entities/logger/ConsoleLogger.java +++ b/src/main/java/us/mytheria/bloblib/entities/logger/ConsoleLogger.java @@ -43,8 +43,22 @@ public void debug(String message) { logger.fine(message); } + /** + * Prints a warning message to the console. + * + * @param throwable the throwable to print + */ + public void throwable(Throwable throwable) { + logger.severe(throwable.getMessage()); + } + + /** + * Prints a warning message to the console. + * + * @param exception the exception to print + */ public void exception(Exception exception) { - logger.severe(exception.getMessage()); + throwable(exception); } /** diff --git a/src/main/java/us/mytheria/bloblib/entities/message/BlobActionbarMessage.java b/src/main/java/us/mytheria/bloblib/entities/message/BlobActionbarMessage.java index e36f82fd..d330dd55 100644 --- a/src/main/java/us/mytheria/bloblib/entities/message/BlobActionbarMessage.java +++ b/src/main/java/us/mytheria/bloblib/entities/message/BlobActionbarMessage.java @@ -4,6 +4,7 @@ import net.md_5.bungee.api.chat.TextComponent; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; import java.util.function.Function; @@ -17,8 +18,9 @@ public class BlobActionbarMessage extends SerialBlobMessage { * @param message The message to send * @param sound The sound to play */ - public BlobActionbarMessage(String message, BlobSound sound) { - super(sound); + public BlobActionbarMessage(String message, BlobSound sound, + String locale) { + super(sound, locale); this.actionbar = message; } @@ -26,7 +28,7 @@ public BlobActionbarMessage(String message, BlobSound sound) { * @param message The message to send */ public BlobActionbarMessage(String message) { - this(message, null); + this(message, null, null); } /** @@ -43,7 +45,7 @@ public void send(Player player) { @Override public void toCommandSender(CommandSender commandSender) { if (commandSender instanceof Player player) - sendAndPlay(player); + handle(player); else commandSender.sendMessage(actionbar); } @@ -53,7 +55,7 @@ public void toCommandSender(CommandSender commandSender) { * @return A new message with the modified message */ @Override - public BlobActionbarMessage modify(Function function) { - return new BlobActionbarMessage(function.apply(actionbar), getSound()); + public @NotNull BlobActionbarMessage modify(Function function) { + return new BlobActionbarMessage(function.apply(actionbar), getSound(), getLocale()); } } diff --git a/src/main/java/us/mytheria/bloblib/entities/message/BlobActionbarTitleMessage.java b/src/main/java/us/mytheria/bloblib/entities/message/BlobActionbarTitleMessage.java index 64655e78..4f04af18 100644 --- a/src/main/java/us/mytheria/bloblib/entities/message/BlobActionbarTitleMessage.java +++ b/src/main/java/us/mytheria/bloblib/entities/message/BlobActionbarTitleMessage.java @@ -4,6 +4,7 @@ import net.md_5.bungee.api.chat.TextComponent; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; import java.util.function.Function; @@ -24,8 +25,9 @@ public class BlobActionbarTitleMessage extends BlobTitleMessage { * @param sound The sound to play */ public BlobActionbarTitleMessage(String actionbar, String title, String subtitle, - int fadeIn, int stay, int fadeOut, BlobSound sound) { - super(title, subtitle, fadeIn, stay, fadeOut, sound); + int fadeIn, int stay, int fadeOut, BlobSound sound, + String locale) { + super(title, subtitle, fadeIn, stay, fadeOut, sound, locale); this.actionbar = actionbar; } @@ -44,7 +46,7 @@ public void send(Player player) { @Override public void toCommandSender(CommandSender commandSender) { if (commandSender instanceof Player player) - sendAndPlay(player); + handle(player); else { commandSender.sendMessage(title); commandSender.sendMessage(subtitle); @@ -57,8 +59,9 @@ public void toCommandSender(CommandSender commandSender) { * @return A new message with the modified message */ @Override - public BlobActionbarTitleMessage modify(Function function) { + public @NotNull BlobActionbarTitleMessage modify(Function function) { return new BlobActionbarTitleMessage(function.apply(actionbar), function.apply(title), - function.apply(subtitle), fadeIn, stay, fadeOut, getSound()); + function.apply(subtitle), fadeIn, stay, fadeOut, getSound(), + getLocale()); } } diff --git a/src/main/java/us/mytheria/bloblib/entities/message/BlobChatActionbarMessage.java b/src/main/java/us/mytheria/bloblib/entities/message/BlobChatActionbarMessage.java index cd5ded79..84456269 100644 --- a/src/main/java/us/mytheria/bloblib/entities/message/BlobChatActionbarMessage.java +++ b/src/main/java/us/mytheria/bloblib/entities/message/BlobChatActionbarMessage.java @@ -4,6 +4,7 @@ import net.md_5.bungee.api.chat.TextComponent; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; import java.util.function.Function; @@ -21,8 +22,9 @@ public class BlobChatActionbarMessage extends BlobChatMessage { * @param actionbar The actionbar message * @param sound The sound to play */ - public BlobChatActionbarMessage(String chat, String actionbar, BlobSound sound) { - super(chat, sound); + public BlobChatActionbarMessage(String chat, String actionbar, BlobSound sound, + String locale) { + super(chat, sound, locale); this.actionbar = actionbar; } @@ -44,7 +46,7 @@ public void send(Player player) { @Override public void toCommandSender(CommandSender commandSender) { if (commandSender instanceof Player player) - sendAndPlay(player); + handle(player); else { commandSender.sendMessage(chat); commandSender.sendMessage(actionbar); @@ -56,7 +58,8 @@ public void toCommandSender(CommandSender commandSender) { * @return a new BlobChatActionbarMessage with the modified chat and actionbar message */ @Override - public BlobChatActionbarMessage modify(Function function) { - return new BlobChatActionbarMessage(function.apply(chat), function.apply(actionbar), getSound()); + public @NotNull BlobChatActionbarMessage modify(Function function) { + return new BlobChatActionbarMessage(function.apply(chat), + function.apply(actionbar), getSound(), getLocale()); } } diff --git a/src/main/java/us/mytheria/bloblib/entities/message/BlobChatActionbarTitleMessage.java b/src/main/java/us/mytheria/bloblib/entities/message/BlobChatActionbarTitleMessage.java index 0849e744..706fe34e 100644 --- a/src/main/java/us/mytheria/bloblib/entities/message/BlobChatActionbarTitleMessage.java +++ b/src/main/java/us/mytheria/bloblib/entities/message/BlobChatActionbarTitleMessage.java @@ -4,6 +4,7 @@ import net.md_5.bungee.api.chat.TextComponent; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; import java.util.function.Function; @@ -29,8 +30,8 @@ public class BlobChatActionbarTitleMessage extends BlobChatMessage { * @param sound The sound to play */ public BlobChatActionbarTitleMessage(String chat, String actionbar, String title, String subtitle, int fadeIn, int stay, int fadeOut, - BlobSound sound) { - super(chat, sound); + BlobSound sound, String locale) { + super(chat, sound, locale); this.actionbar = actionbar; this.title = title; this.subtitle = subtitle; @@ -59,7 +60,7 @@ public void send(Player player) { @Override public void toCommandSender(CommandSender commandSender) { if (commandSender instanceof Player player) - sendAndPlay(player); + handle(player); else { commandSender.sendMessage(chat); commandSender.sendMessage(title); @@ -73,8 +74,9 @@ public void toCommandSender(CommandSender commandSender) { * @return a new BlobChatActionbarMessage with the modified chat and actionbar message */ @Override - public BlobChatActionbarTitleMessage modify(Function function) { + public @NotNull BlobChatActionbarTitleMessage modify(Function function) { return new BlobChatActionbarTitleMessage(function.apply(chat), function.apply(actionbar), function.apply(title), - function.apply(subtitle), fadeIn, stay, fadeOut, getSound()); + function.apply(subtitle), fadeIn, stay, fadeOut, getSound(), + getLocale()); } } diff --git a/src/main/java/us/mytheria/bloblib/entities/message/BlobChatMessage.java b/src/main/java/us/mytheria/bloblib/entities/message/BlobChatMessage.java index 0df72256..66141606 100644 --- a/src/main/java/us/mytheria/bloblib/entities/message/BlobChatMessage.java +++ b/src/main/java/us/mytheria/bloblib/entities/message/BlobChatMessage.java @@ -2,6 +2,7 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; import java.util.function.Function; @@ -21,8 +22,8 @@ public class BlobChatMessage extends SerialBlobMessage { * @param message The chat message * @param sound The sound to play */ - public BlobChatMessage(String message, BlobSound sound) { - super(sound); + public BlobChatMessage(String message, BlobSound sound, String locale) { + super(sound, locale); this.chat = message; } @@ -53,7 +54,7 @@ public void send(CommandSender sender) { @Override public void toCommandSender(CommandSender commandSender) { if (commandSender instanceof Player player) - sendAndPlay(player); + handle(player); else commandSender.sendMessage(chat); } @@ -63,7 +64,8 @@ public void toCommandSender(CommandSender commandSender) { * @return a new BlobChatMessage with the modified chat message */ @Override - public BlobChatMessage modify(Function function) { - return new BlobChatMessage(function.apply(chat), getSound()); + public @NotNull BlobChatMessage modify(Function function) { + return new BlobChatMessage(function.apply(chat), getSound(), + getLocale()); } } diff --git a/src/main/java/us/mytheria/bloblib/entities/message/BlobChatTitleMessage.java b/src/main/java/us/mytheria/bloblib/entities/message/BlobChatTitleMessage.java index d589e1dc..9739474a 100644 --- a/src/main/java/us/mytheria/bloblib/entities/message/BlobChatTitleMessage.java +++ b/src/main/java/us/mytheria/bloblib/entities/message/BlobChatTitleMessage.java @@ -2,6 +2,7 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; import java.util.function.Function; @@ -24,8 +25,8 @@ public class BlobChatTitleMessage extends BlobChatMessage { * @param sound the sound to play */ public BlobChatTitleMessage(String chat, String title, String subtitle, int fadeIn, int stay, int fadeOut, - BlobSound sound) { - super(chat, sound); + BlobSound sound, String locale) { + super(chat, sound, locale); this.title = title; this.subtitle = subtitle; this.fadeIn = fadeIn; @@ -48,7 +49,7 @@ public void send(Player player) { @Override public void toCommandSender(CommandSender commandSender) { if (commandSender instanceof Player player) - sendAndPlay(player); + handle(player); else { commandSender.sendMessage(chat); commandSender.sendMessage(title); @@ -61,8 +62,8 @@ public void toCommandSender(CommandSender commandSender) { * @return a new BlobChatTitleMessage with the modified message */ @Override - public BlobChatTitleMessage modify(Function function) { + public @NotNull BlobChatTitleMessage modify(Function function) { return new BlobChatTitleMessage(function.apply(chat), function.apply(title), function.apply(subtitle), - fadeIn, stay, fadeOut, getSound()); + fadeIn, stay, fadeOut, getSound(), getLocale()); } } diff --git a/src/main/java/us/mytheria/bloblib/entities/message/BlobMessage.java b/src/main/java/us/mytheria/bloblib/entities/message/BlobMessage.java index 05caf6f5..14122b1e 100644 --- a/src/main/java/us/mytheria/bloblib/entities/message/BlobMessage.java +++ b/src/main/java/us/mytheria/bloblib/entities/message/BlobMessage.java @@ -1,7 +1,12 @@ package us.mytheria.bloblib.entities.message; +import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import us.mytheria.bloblib.entities.BlobMessageModder; import java.util.function.Function; @@ -22,30 +27,137 @@ * plugins and even the same server administrator can use them. */ public interface BlobMessage { - /** + * Will send the message to the player. + * * @param player The player to send the message to + * @deprecated Use {@link #handle(Player)} instead */ + @Deprecated void send(Player player); /** + * If sound is not null, it will play the sound to the player. + * + * @param player The player to send the message to + * @param location The location to play the sound at + * @deprecated Use {@link #handle(Player)} instead + */ + @Deprecated + default void sendAndPlay(Player player, Location location) { + send(player); + if (getSound() != null) + getSound().play(player, location); + } + + /** + * If sound is not null, it will play the sound to the player. + * Would be played at the player's location. + * + * @param player The player to send the message to + * @deprecated Use {@link #handle(Player)} instead + */ + @Deprecated + default void sendAndPlay(Player player) { + sendAndPlay(player, player.getLocation()); + } + + /** + * If the sound is not null, it will play the sound at player's location + * and nearby players will be able to hear it. + * + * @param player The player to send the message to + * @param location The location to play the sound at + * @deprecated Use {@link #handle(Player)} instead + */ + @Deprecated + default void sendAndPlayInWorld(Player player, Location location) { + send(player); + if (getSound() != null) + getSound().playInWorld(location); + } + + /** + * If the sound is not null, it will play the sound at player's location + * and nearby players will be able to hear it. + * Would be played at the player's location. + * * @param player The player to send the message to + * @deprecated Use {@link #handle(Player)} instead + */ + @Deprecated + default void sendAndPlayInWorld(Player player) { + sendAndPlayInWorld(player, player.getLocation()); + } + + /** + * Will handle the message with the required settings for the player + * such as not having a sound, if having sound playing just + * to the player, or playing to the whole world, etc. + * + * @param player The player to handle the message for + * @param location The location to play the sound at + */ + default void handle(Player player, Location location) { + if (getSound() == null) + send(player); + else if (getSound().audience() == MessageAudience.PLAYER) + sendAndPlay(player, location); + else + sendAndPlayInWorld(player, location); + } + + /** + * Will handle the message with the required settings for the player + * such as not having a sound, if having sound playing just + * to the player, or playing to the whole world, etc. + * Would be played at the player's location. + * + * @param player The player to handle the message for + */ + default void handle(Player player) { + handle(player, player.getLocation()); + } + + /** + * Will handle the message to all online players. */ - void sendAndPlay(Player player); + default void broadcast() { + Bukkit.getOnlinePlayers().forEach(this::handle); + } /** + * Will send the message to the command sender. + * * @param commandSender The command sender to send the message to */ void toCommandSender(CommandSender commandSender); /** + * Will retrieve the BlobSound object. + * * @return The sound to play */ + @Nullable BlobSound getSound(); /** * @param function The function to modify the message with * @return A new message with the modified message */ + @NotNull BlobMessage modify(Function function); + + @NotNull + default BlobMessageModder modder() { + return BlobMessageModder.mod(this); + } + + /** + * Will retrieve the locale of the message. + * + * @return The locale of the message + */ + @Nullable + String getLocale(); } diff --git a/src/main/java/us/mytheria/bloblib/entities/message/BlobSound.java b/src/main/java/us/mytheria/bloblib/entities/message/BlobSound.java index 3b5bb7db..04180279 100644 --- a/src/main/java/us/mytheria/bloblib/entities/message/BlobSound.java +++ b/src/main/java/us/mytheria/bloblib/entities/message/BlobSound.java @@ -1,16 +1,109 @@ package us.mytheria.bloblib.entities.message; +import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.Sound; import org.bukkit.SoundCategory; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; import javax.annotation.Nullable; -public record BlobSound(Sound sound, float volume, float pitch, @Nullable SoundCategory soundCategory) { +public record BlobSound(Sound sound, float volume, float pitch, + @Nullable SoundCategory soundCategory, + @NotNull MessageAudience audience) { + /** + * Plays the sound to the player at the given location + * + * @param player The player to play the sound to + * @param location The location to play the sound at + */ + public void play(Player player, Location location) { + if (soundCategory == null) + player.playSound(location, sound, volume, pitch); + else + player.playSound(location, sound, soundCategory, volume, pitch); + } + + /** + * Plays the sound to the player at the player's location + * + * @param player The player to play the sound to + */ public void play(Player player) { + play(player, player.getLocation()); + } + + /** + * Plays the sound to the world at the given location + * + * @param location The location to play the sound at + */ + public void playInWorld(Location location) { if (soundCategory == null) - player.playSound(player.getLocation(), sound, volume, pitch); + location.getWorld().playSound(location, sound, volume, pitch); + else + location.getWorld().playSound(location, sound, soundCategory, volume, pitch); + } + + /** + * Handles the sound at the given location + * + * @param player The player that's linked to the sound + * @param location The location to play the sound at + */ + public void handle(Player player, Location location) { + if (audience == MessageAudience.PLAYER) + play(player, location); else - player.playSound(player.getLocation(), sound, soundCategory, volume, pitch); + playInWorld(location); + } + + /** + * Handles the sound at the player's location + * + * @param player The player that's linked to the sound + */ + public void handle(Player player) { + if (audience == MessageAudience.PLAYER) + play(player); + else + playInWorld(player.getLocation()); + } + + /** + * Handles the sound at the entity's location + * If the entity is a player, it will play the sound to the player + * or to the world depending on the audience + * + * @param entity The entity that's linked to the sound + */ + public void handle(Entity entity) { + EntityType entityType = entity.getType(); + if (entityType == EntityType.PLAYER) { + Player player = (Player) entity; + handle(player); + return; + } + playInWorld(entity.getLocation()); + } + + /** + * Handles the sound at the block's location + * + * @param block The block that's linked to the sound + */ + public void handle(Block block) { + playInWorld(block.getLocation()); + } + + /** + * Will handle the sound to all online players. + */ + public void broadcast() { + Bukkit.getOnlinePlayers().forEach(this::handle); } } diff --git a/src/main/java/us/mytheria/bloblib/entities/message/BlobTitleMessage.java b/src/main/java/us/mytheria/bloblib/entities/message/BlobTitleMessage.java index a37369d4..78aea18c 100644 --- a/src/main/java/us/mytheria/bloblib/entities/message/BlobTitleMessage.java +++ b/src/main/java/us/mytheria/bloblib/entities/message/BlobTitleMessage.java @@ -2,6 +2,7 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; import java.util.function.Function; @@ -10,8 +11,8 @@ public class BlobTitleMessage extends SerialBlobMessage { protected final int fadeIn, stay, fadeOut; public BlobTitleMessage(String title, String subtitle, int fadeIn, int stay, int fadeOut, - BlobSound sound) { - super(sound); + BlobSound sound, String locale) { + super(sound, locale); this.title = title; this.subtitle = subtitle; this.fadeIn = fadeIn; @@ -27,7 +28,7 @@ public void send(Player player) { @Override public void toCommandSender(CommandSender commandSender) { if (commandSender instanceof Player player) - sendAndPlay(player); + handle(player); else { commandSender.sendMessage(title); commandSender.sendMessage(subtitle); @@ -35,8 +36,8 @@ public void toCommandSender(CommandSender commandSender) { } @Override - public BlobTitleMessage modify(Function function) { + public @NotNull BlobTitleMessage modify(Function function) { return new BlobTitleMessage(function.apply(title), function.apply(subtitle), fadeIn, stay, - fadeOut, getSound()); + fadeOut, getSound(), getLocale()); } } diff --git a/src/main/java/us/mytheria/bloblib/entities/message/BungeeMessage.java b/src/main/java/us/mytheria/bloblib/entities/message/BungeeMessage.java deleted file mode 100644 index 83212327..00000000 --- a/src/main/java/us/mytheria/bloblib/entities/message/BungeeMessage.java +++ /dev/null @@ -1,62 +0,0 @@ -package us.mytheria.bloblib.entities.message; - -import javax.annotation.Nullable; -import java.io.*; - -public class BungeeMessage implements Serializable { - - public static byte[] serialize(BungeeMessage message) { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutput out = null; - try { - out = new ObjectOutputStream(bos); - out.writeObject(message); - byte b[] = bos.toByteArray(); - out.close(); - bos.close(); - return b; - } catch (IOException e) { - e.printStackTrace(); - } - return null; - } - - @Nullable - public static BungeeMessage deserialize(byte[] bytes) { - BungeeMessage message; - if (bytes == null) - return null; - ByteArrayInputStream bis = new ByteArrayInputStream(bytes); - ObjectInput in = null; - try { - in = new ObjectInputStream(bis); - message = (BungeeMessage) in.readObject(); - return message; - } catch (IOException | ClassNotFoundException e) { - e.printStackTrace(); - } - return null; - } - - private String key; - private String type; - private Serializable value; - - public BungeeMessage(String key, String type, Serializable value) { - this.key = key; - this.type = type; - this.value = value; - } - - public String getKey() { - return key; - } - - public String getType() { - return type; - } - - public Serializable getValue() { - return value; - } -} diff --git a/src/main/java/us/mytheria/bloblib/entities/message/MessageAudience.java b/src/main/java/us/mytheria/bloblib/entities/message/MessageAudience.java new file mode 100644 index 00000000..6680ea52 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/message/MessageAudience.java @@ -0,0 +1,6 @@ +package us.mytheria.bloblib.entities.message; + +public enum MessageAudience { + PLAYER, + WORLD +} diff --git a/src/main/java/us/mytheria/bloblib/entities/message/ReferenceBlobMessage.java b/src/main/java/us/mytheria/bloblib/entities/message/ReferenceBlobMessage.java index 030bbbfe..f1da5453 100644 --- a/src/main/java/us/mytheria/bloblib/entities/message/ReferenceBlobMessage.java +++ b/src/main/java/us/mytheria/bloblib/entities/message/ReferenceBlobMessage.java @@ -2,6 +2,8 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.function.Function; @@ -19,11 +21,18 @@ public void send(Player player) { message.send(player); } + @SuppressWarnings("deprecation") @Override public void sendAndPlay(Player player) { message.sendAndPlay(player); } + @SuppressWarnings("deprecation") + @Override + public void sendAndPlayInWorld(Player player) { + message.sendAndPlayInWorld(player); + } + @Override public void toCommandSender(CommandSender commandSender) { message.toCommandSender(commandSender); @@ -35,11 +44,22 @@ public BlobSound getSound() { } @Override - public ReferenceBlobMessage modify(Function function) { + public @NotNull ReferenceBlobMessage modify(Function function) { return new ReferenceBlobMessage(message.modify(function), reference); } public String getReference() { return reference; } + + /** + * Will retrieve the locale of the message. + * + * @return The locale of the message + */ + @Override + @Nullable + public String getLocale() { + return message.getLocale(); + } } diff --git a/src/main/java/us/mytheria/bloblib/entities/message/SerialBlobMessage.java b/src/main/java/us/mytheria/bloblib/entities/message/SerialBlobMessage.java index ae23cb68..543d8911 100644 --- a/src/main/java/us/mytheria/bloblib/entities/message/SerialBlobMessage.java +++ b/src/main/java/us/mytheria/bloblib/entities/message/SerialBlobMessage.java @@ -2,38 +2,52 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.function.Function; public abstract class SerialBlobMessage implements BlobMessage { private final BlobSound sound; + private final String locale; - public SerialBlobMessage(BlobSound sound) { + public SerialBlobMessage(BlobSound sound, String locale) { this.sound = sound; + this.locale = locale; } public SerialBlobMessage() { sound = null; + locale = null; } @Override public abstract void send(Player player); - @Override - public void sendAndPlay(Player player) { - send(player); - if (sound != null) - sound.play(player); - } - @Override public abstract void toCommandSender(CommandSender commandSender); + /** + * Will retrieve the BlobSound object. + * + * @return The sound to play + */ @Override public BlobSound getSound() { return sound; } + /** + * Will retrieve the locale of the message. + * + * @return The locale of the message + */ + @Override + @Nullable + public String getLocale() { + return locale; + } + @Override - public abstract SerialBlobMessage modify(Function function); + public abstract @NotNull SerialBlobMessage modify(Function function); } diff --git a/src/main/java/us/mytheria/bloblib/entities/proxy/BlobProxifier.java b/src/main/java/us/mytheria/bloblib/entities/proxy/BlobProxifier.java new file mode 100644 index 00000000..bd4b8263 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/proxy/BlobProxifier.java @@ -0,0 +1,35 @@ +package us.mytheria.bloblib.entities.proxy; + +import us.mytheria.bloblib.entities.IFileManager; +import us.mytheria.bloblib.managers.IManagerDirector; + +/** + * There's no issue with proxying IFileManager and IManagerDirector + * since their usage is minimal, efficient and unless you call + * the Proxy's methods hundreds of thousands of times in the same tick, + * you won't notice any performance issues... + * Just to clarify, if providing a proxied ManagerDirector + * as the ManagerDirector inside the BlobPlugin class, + * you won't notice any performance issues. + */ +public class BlobProxifier { + /** + * Will proxy the given IFileManager. + * + * @param fileManager The IFileManager to proxy. + * @return The proxied IFileManager. + */ + public static IFileManager PROXY(IFileManager fileManager) { + return new IFileManagerProxy(fileManager); + } + + /** + * Will proxy the given IManagerDirector. + * + * @param managerDirector The IManagerDirector to proxy. + * @return The proxied IManagerDirector. + */ + public static IManagerDirector PROXY(IManagerDirector managerDirector) { + return new IManagerDirectorProxy(managerDirector); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/proxy/IFileManagerProxy.java b/src/main/java/us/mytheria/bloblib/entities/proxy/IFileManagerProxy.java new file mode 100644 index 00000000..0d567530 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/proxy/IFileManagerProxy.java @@ -0,0 +1,33 @@ +package us.mytheria.bloblib.entities.proxy; + +import us.mytheria.bloblib.entities.IFileManager; + +import java.io.File; + +public class IFileManagerProxy implements IFileManager { + private final IFileManager fileManager; + + protected IFileManagerProxy(IFileManager fileManager) { + this.fileManager = fileManager; + } + + public File messagesDirectory() { + return fileManager.messagesDirectory(); + } + + public File soundsDirectory() { + return fileManager.soundsDirectory(); + } + + public File inventoriesDirectory() { + return fileManager.inventoriesDirectory(); + } + + public File metaInventoriesDirectory() { + return fileManager.metaInventoriesDirectory(); + } + + public File actionsDirectory() { + return fileManager.actionsDirectory(); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/proxy/IManagerDirectorProxy.java b/src/main/java/us/mytheria/bloblib/entities/proxy/IManagerDirectorProxy.java new file mode 100644 index 00000000..cf493dd9 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/proxy/IManagerDirectorProxy.java @@ -0,0 +1,28 @@ +package us.mytheria.bloblib.entities.proxy; + +import us.mytheria.bloblib.entities.IFileManager; +import us.mytheria.bloblib.managers.IManagerDirector; + +public class IManagerDirectorProxy implements IManagerDirector { + private final IManagerDirector managerDirector; + + protected IManagerDirectorProxy(IManagerDirector managerDirector) { + this.managerDirector = managerDirector; + } + + public void unload() { + managerDirector.unload(); + } + + public void postWorld() { + managerDirector.postWorld(); + } + + public void reload() { + managerDirector.reload(); + } + + public IFileManager getFileManager() { + return managerDirector.getFileManager(); + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/worker/AbsentListenerWorker.java b/src/main/java/us/mytheria/bloblib/entities/worker/AbsentListenerWorker.java new file mode 100644 index 00000000..fae97956 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/worker/AbsentListenerWorker.java @@ -0,0 +1,13 @@ +package us.mytheria.bloblib.entities.worker; + +public class AbsentListenerWorker implements ListenerWorker { + @Override + public void load() { + + } + + @Override + public void unload() { + + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/worker/AbsentWorker.java b/src/main/java/us/mytheria/bloblib/entities/worker/AbsentWorker.java new file mode 100644 index 00000000..acf34332 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/worker/AbsentWorker.java @@ -0,0 +1,13 @@ +package us.mytheria.bloblib.entities.worker; + +public class AbsentWorker implements Worker { + @Override + public void load() { + + } + + @Override + public void unload() { + + } +} diff --git a/src/main/java/us/mytheria/bloblib/entities/worker/ListenerWorker.java b/src/main/java/us/mytheria/bloblib/entities/worker/ListenerWorker.java new file mode 100644 index 00000000..6819452c --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/worker/ListenerWorker.java @@ -0,0 +1,6 @@ +package us.mytheria.bloblib.entities.worker; + +import org.bukkit.event.Listener; + +public interface ListenerWorker extends Listener, Worker { +} diff --git a/src/main/java/us/mytheria/bloblib/entities/worker/Worker.java b/src/main/java/us/mytheria/bloblib/entities/worker/Worker.java new file mode 100644 index 00000000..e5edc0d6 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/entities/worker/Worker.java @@ -0,0 +1,8 @@ +package us.mytheria.bloblib.entities.worker; + +public interface Worker { + + void load(); + + void unload(); +} diff --git a/src/main/java/us/mytheria/bloblib/exception/ConfigurationFieldException.java b/src/main/java/us/mytheria/bloblib/exception/ConfigurationFieldException.java new file mode 100644 index 00000000..f6e20d3f --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/exception/ConfigurationFieldException.java @@ -0,0 +1,25 @@ +package us.mytheria.bloblib.exception; + +import java.io.File; + +public class ConfigurationFieldException extends RuntimeException { + public ConfigurationFieldException(String message) { + super(message); + } + + public ConfigurationFieldException(String message, File file) { + this(file == null ? message : message + " \n At: " + file.getPath()); + } + + public ConfigurationFieldException(String message, Throwable cause) { + super(message, cause); + } + + public ConfigurationFieldException(String message, File file, Throwable cause) { + this(file == null ? message : message + " \n At: " + file.getPath(), cause); + } + + public ConfigurationFieldException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/us/mytheria/bloblib/exception/InterpretationException.java b/src/main/java/us/mytheria/bloblib/exception/InterpretationException.java new file mode 100644 index 00000000..485ba4b4 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/exception/InterpretationException.java @@ -0,0 +1,68 @@ +package us.mytheria.bloblib.exception; + +import java.io.File; + +public class InterpretationException extends RuntimeException { + public static InterpretationException INVALID_ARGUMENT() { + return new InterpretationException(ExceptionType.INVALID_ARGUMENT); + } + + public static InterpretationException INVALID_ARGUMENT(String extra) { + return new InterpretationException(ExceptionType.INVALID_ARGUMENT, extra); + } + + public static InterpretationException INVALID_OPERATION() { + return new InterpretationException(ExceptionType.INVALID_OPERATION); + } + + public static InterpretationException INVALID_OPERATION(String extra) { + return new InterpretationException(ExceptionType.INVALID_OPERATION, extra); + } + + public static InterpretationException INVALID_INPUT() { + return new InterpretationException(ExceptionType.INVALID_INPUT); + } + + public static InterpretationException INVALID_INPUT(String extra) { + return new InterpretationException(ExceptionType.INVALID_INPUT, extra); + } + + public enum ExceptionType { + INVALID_ARGUMENT("Invalid argument"), + INVALID_OPERATION("Invalid operation"), + INVALID_INPUT("Invalid input"); + private final String message; + + ExceptionType(String message) { + this.message = message; + } + } + + public InterpretationException(ExceptionType exceptionType, String extra) { + super(exceptionType.message + ": " + extra); + } + + public InterpretationException(ExceptionType exceptionType) { + super(exceptionType.message); + } + + public InterpretationException(String message) { + super(message); + } + + public InterpretationException(String message, File file) { + this(file == null ? message : message + " \n At: " + file.getPath()); + } + + public InterpretationException(String message, Throwable cause) { + super(message, cause); + } + + public InterpretationException(String message, File file, Throwable cause) { + this(file == null ? message : message + " \n At: " + file.getPath(), cause); + } + + public InterpretationException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/us/mytheria/bloblib/floatingpet/FloatingPetData.java b/src/main/java/us/mytheria/bloblib/floatingpet/FloatingPetData.java deleted file mode 100644 index b8b2b71d..00000000 --- a/src/main/java/us/mytheria/bloblib/floatingpet/FloatingPetData.java +++ /dev/null @@ -1,59 +0,0 @@ -package us.mytheria.bloblib.floatingpet; - -import org.bukkit.Particle; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.Nullable; - -public class FloatingPetData { - private final ItemStack itemStack; - @Nullable - private final Particle particle; - @Nullable - private final String customName; - - /** - * Will hold all attributes for a FloatingPet - * except the owner themselves. The idea behind - * it is that FloatingPetData could be stored - * in a manager and later used to make new instances - * of FloatingPets. - * - * @param record the FloatingPetRecord to pass from - */ - public FloatingPetData(FloatingPetRecord record) { - this.itemStack = record.itemStack(); - this.particle = record.particle(); - this.customName = record.customName(); - } - - public FloatingPet toPlayer(Player owner) { - return new FloatingPet(owner, itemStack, particle, customName); - } - - public FloatingPet toPlayerAndParsePlaceholder(Player owner, String ownerPlaceholder) { - FloatingPet floatingPet = toPlayer(owner); - floatingPet.setCustomName(floatingPet.getCustomName().replace(ownerPlaceholder, owner.getName())); - return floatingPet; - } - - public ItemStack getItemStack() { - return itemStack; - } - - @Nullable - public Particle getParticle() { - return particle; - } - - @Nullable - public String getCustomName() { - return customName; - } - - public void serialize(ConfigurationSection configurationSection, String path) { - FloatingPetRecord record = new FloatingPetRecord(itemStack, particle, customName); - record.serialize(configurationSection, path); - } -} diff --git a/src/main/java/us/mytheria/bloblib/floatingpet/FloatingPetRecord.java b/src/main/java/us/mytheria/bloblib/floatingpet/FloatingPetRecord.java deleted file mode 100644 index ea1ff372..00000000 --- a/src/main/java/us/mytheria/bloblib/floatingpet/FloatingPetRecord.java +++ /dev/null @@ -1,43 +0,0 @@ -package us.mytheria.bloblib.floatingpet; - -import org.bukkit.Particle; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.inventory.ItemStack; -import us.mytheria.bloblib.itemstack.ItemStackBuilder; -import us.mytheria.bloblib.itemstack.ItemStackReader; -import us.mytheria.bloblib.utilities.SerializationLib; -import us.mytheria.bloblib.utilities.TextColor; - -/** - * A data carrier for the basic FloatingPet data. - * The idea for a more complex data carrier is that - * you extend FloatingPetData and implement your - * own FloatingPetReader which will inject into - * the FloatingPetData. - * - * @param itemStack the ItemStack to display - * @param particle the particle to display - * @param customName the custom name to display - */ -public record FloatingPetRecord(ItemStack itemStack, Particle particle, String customName) { - public static FloatingPetRecord read(ConfigurationSection section) { - ItemStackBuilder builder = ItemStackReader.read(section.getConfigurationSection("ItemStack")); - ItemStack itemStack = builder.build(); - Particle particle = null; - if (section.contains("Particle")) - particle = SerializationLib.deserializeParticle(section.getString("Particle")); - String customName = null; - if (section.contains("CustomName")) - customName = TextColor.PARSE(section.getString("CustomName")); - - return new FloatingPetRecord(itemStack, particle, customName); - } - - public void serialize(ConfigurationSection configurationSection, String path) { - configurationSection.set(path + ".ItemStack", itemStack); - if (particle != null) - configurationSection.set(path + ".Particle", particle); - if (customName != null) - configurationSection.set(path + ".CustomName", customName); - } -} diff --git a/src/main/java/us/mytheria/bloblib/itemstack/ItemStackBuilder.java b/src/main/java/us/mytheria/bloblib/itemstack/ItemStackBuilder.java index b8fa76d2..1b8f5c1d 100644 --- a/src/main/java/us/mytheria/bloblib/itemstack/ItemStackBuilder.java +++ b/src/main/java/us/mytheria/bloblib/itemstack/ItemStackBuilder.java @@ -4,16 +4,20 @@ import org.bukkit.Color; import org.bukkit.Material; import org.bukkit.NamespacedKey; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeModifier; import org.bukkit.enchantments.Enchantment; import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.LeatherArmorMeta; import us.mytheria.bloblib.entities.Rep; +import us.mytheria.bloblib.utilities.TextColor; import java.util.*; import java.util.function.Consumer; import java.util.function.Function; +import java.util.logging.Level; public final class ItemStackBuilder { private final ItemStack itemStack; @@ -96,16 +100,23 @@ public ItemStackBuilder unflag(Collection flags) { return unflag(flags.toArray(new ItemFlag[0])); } + public ItemStackBuilder displayName(String name, char translateColorChar) { + return itemMeta(itemMeta -> itemMeta.setDisplayName(TextColor.CUSTOM_PARSE(translateColorChar, name))); + } + public ItemStackBuilder displayName(String name) { - return itemMeta(itemMeta -> itemMeta.setDisplayName(name)); + return displayName(name, '&'); } public ItemStackBuilder lore(String line) { - return lore(List.of(line)); + return lore(List.of(TextColor.PARSE(line))); } public ItemStackBuilder lore(String... lore) { - return itemMeta(itemMeta -> itemMeta.setLore(List.of(lore))); + List list = List.of(lore); + List dupe = new ArrayList<>(); + list.forEach(s -> dupe.add(TextColor.PARSE(s))); + return itemMeta(itemMeta -> itemMeta.setLore(dupe)); } public ItemStackBuilder lore(List lore) { @@ -245,6 +256,18 @@ public ItemStackBuilder color(Color color) { }); } + public ItemStackBuilder attribute(Attribute attribute, double amount, AttributeModifier.Operation operation) { + return itemMeta(itemMeta -> { + try { + UUID uuid = UUID.randomUUID(); + AttributeModifier modifier = new AttributeModifier(uuid, uuid.toString(), amount, operation); + itemMeta.addAttributeModifier(attribute, modifier); + } catch (Exception exception) { + Bukkit.getLogger().log(Level.SEVERE, exception, () -> "Failed to add attribute modifier"); + } + }); + } + public ItemStackBuilder unbreakable(boolean unbreakable) { return itemMeta(itemMeta -> itemMeta.setUnbreakable(unbreakable)); } diff --git a/src/main/java/us/mytheria/bloblib/itemstack/ItemStackReader.java b/src/main/java/us/mytheria/bloblib/itemstack/ItemStackReader.java index a44b6549..b8d84347 100644 --- a/src/main/java/us/mytheria/bloblib/itemstack/ItemStackReader.java +++ b/src/main/java/us/mytheria/bloblib/itemstack/ItemStackReader.java @@ -1,12 +1,17 @@ package us.mytheria.bloblib.itemstack; -import net.md_5.bungee.api.ChatColor; +import me.anjoismysign.anjo.entities.Uber; import org.bukkit.Bukkit; import org.bukkit.Color; import org.bukkit.Material; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeModifier; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.YamlConfiguration; +import org.jetbrains.annotations.Nullable; import us.mytheria.bloblib.SkullCreator; +import us.mytheria.bloblib.exception.ConfigurationFieldException; +import us.mytheria.bloblib.utilities.TextColor; import java.io.File; import java.util.ArrayList; @@ -15,60 +20,91 @@ public class ItemStackReader { - public static ItemStackBuilder read(ConfigurationSection section) { - String inputMaterial = section.getString("Material", "DIRT"); + public static ItemStackBuilder READ_OR_FAIL_FAST(ConfigurationSection section) { + if (!section.isString("Material")) + throw new ConfigurationFieldException("'Material' field is missing or not a String"); + String inputMaterial = section.getString("Material"); ItemStackBuilder builder; if (!inputMaterial.startsWith("HEAD-")) { Material material = Material.getMaterial(inputMaterial); - if (material == null) { - Bukkit.getLogger().severe("Material " + inputMaterial + " is not a valid material. Using DIRT instead."); - material = Material.DIRT; - } + if (material == null) + throw new ConfigurationFieldException("'Material' field is not a valid material"); builder = ItemStackBuilder.build(material); } else builder = ItemStackBuilder.build(SkullCreator.itemFromUrl(inputMaterial.substring(5))); - - if (section.contains("Amount")) { + if (section.isInt("Amount")) { builder = builder.amount(section.getInt("Amount")); } - if (section.contains("DisplayName")) { - builder = builder.displayName(ChatColor - .translateAlternateColorCodes('&', section - .getString("DisplayName"))); + if (section.isString("DisplayName")) { + builder = builder.displayName(TextColor.PARSE(section + .getString("DisplayName"))); } - if (section.contains("Lore")) { + if (section.isList("Lore")) { List input = section.getStringList("Lore"); List lore = new ArrayList<>(); - input.forEach(string -> lore.add(ChatColor - .translateAlternateColorCodes('&', string))); + input.forEach(string -> lore.add(TextColor.PARSE(string))); builder = builder.lore(lore); } - if (section.contains("Unbreakable")) { + if (section.isBoolean("Unbreakable")) { builder = builder.unbreakable(section.getBoolean("Unbreakable")); } - if (section.contains("Color")) { + if (section.isString("Color")) { builder = builder.color(parseColor(section.getString("Color"))); } - if (section.contains("Enchantments")) { + if (section.isList("Enchantments")) { List enchantNames = section.getStringList("Enchantments"); builder = builder.deserializeAndEnchant(enchantNames); } - if (section.contains("CustomModelData")) { + if (section.isInt("CustomModelData")) { builder = builder.customModelData(section.getInt("CustomModelData")); } boolean showAll = section.getBoolean("ShowAllItemFlags", false); if (showAll) builder = builder.showAll(); - if (section.contains("ItemFlags")) { + if (section.isList("ItemFlags")) { List flagNames = section.getStringList("ItemFlags"); builder = builder.deserializeAndFlag(flagNames); } + if (section.isConfigurationSection("Attributes")) { + ConfigurationSection attributes = section.getConfigurationSection("Attributes"); + Uber uber = Uber.drive(builder); + attributes.getKeys(false).forEach(key -> { + if (!attributes.isConfigurationSection(key)) + throw new ConfigurationFieldException("Attribute '" + key + "' is not valid"); + ConfigurationSection attributeSection = attributes.getConfigurationSection(key); + try { + Attribute attribute = Attribute.valueOf(key); + if (!attributeSection.isDouble("Amount")) + throw new ConfigurationFieldException("Attribute '" + key + "' has an invalid amount (DECIMAL NUMBER)"); + double amount = attributeSection.getDouble("Amount"); + if (!attributeSection.isString("Operation")) + throw new ConfigurationFieldException("Attribute '" + key + "' is missing 'Operation' field"); + AttributeModifier.Operation operation = AttributeModifier.Operation.valueOf(attributeSection.getString("Operation")); + uber.talk(uber.thanks().attribute(attribute, amount, operation)); + } catch (IllegalArgumentException e) { + throw new ConfigurationFieldException("Attribute '" + key + "' has an invalid Operation"); + } + }); + builder = uber.thanks(); + } return builder; } + @Nullable + public static ItemStackBuilder read(ConfigurationSection section) { + ItemStackBuilder builder; + try { + builder = READ_OR_FAIL_FAST(section); + return builder; + } catch (Exception e) { + Bukkit.getLogger().severe(e.getMessage()); + return ItemStackBuilder.build(Material.DIRT); + } + } + public static ItemStackBuilder read(File file, String path) { YamlConfiguration config = YamlConfiguration.loadConfiguration(file); - return read(Objects.requireNonNull(config.getConfigurationSection(path))); + return READ_OR_FAIL_FAST(Objects.requireNonNull(config.getConfigurationSection(path))); } public static ItemStackBuilder read(File file) { @@ -76,7 +112,7 @@ public static ItemStackBuilder read(File file) { } public static ItemStackBuilder read(YamlConfiguration config, String path) { - return read(Objects.requireNonNull(config.getConfigurationSection(path))); + return READ_OR_FAIL_FAST(Objects.requireNonNull(config.getConfigurationSection(path))); } public static ItemStackBuilder read(YamlConfiguration config) { diff --git a/src/main/java/us/mytheria/bloblib/listeners/DisplayUnriding.java b/src/main/java/us/mytheria/bloblib/listeners/DisplayUnriding.java new file mode 100644 index 00000000..6b76fa22 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/listeners/DisplayUnriding.java @@ -0,0 +1,31 @@ +package us.mytheria.bloblib.listeners; + +import org.bukkit.Bukkit; +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.vehicle.VehicleExitEvent; +import us.mytheria.bloblib.BlobLib; +import us.mytheria.bloblib.managers.BlobLibConfigManager; + +public class DisplayUnriding implements Listener { + private final BlobLibConfigManager configManager; + + public DisplayUnriding(BlobLibConfigManager configManager) { + this.configManager = configManager; + } + + public void reload() { + HandlerList.unregisterAll(this); + if (configManager.getDisplayRiding().register()) { + Bukkit.getPluginManager().registerEvents(this, BlobLib.getInstance()); + } + } + + @EventHandler + public void handle(VehicleExitEvent e) { + if (!e.getExited().getType().toString().contains("DISPLAY")) + return; + e.setCancelled(true); + } +} diff --git a/src/main/java/us/mytheria/bloblib/managers/ActionManager.java b/src/main/java/us/mytheria/bloblib/managers/ActionManager.java new file mode 100644 index 00000000..04448cbe --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/managers/ActionManager.java @@ -0,0 +1,160 @@ +package us.mytheria.bloblib.managers; + +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Entity; +import us.mytheria.bloblib.BlobLib; +import us.mytheria.bloblib.action.Action; + +import java.io.File; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +public class ActionManager { + private final BlobLib main; + private HashMap> actions; + private HashMap> pluginActions; + private HashMap duplicates; + + public ActionManager() { + this.main = BlobLib.getInstance(); + } + + public void reload() { + load(); + } + + public void load() { + actions = new HashMap<>(); + pluginActions = new HashMap<>(); + duplicates = new HashMap<>(); + loadFiles(main.getFileManager().actionsDirectory()); + duplicates.forEach((key, value) -> BlobLib.getAnjoLogger() + .log("Duplicate Action: '" + key + "' (found " + value + " instances)")); + } + + public void load(BlobPlugin plugin, IManagerDirector director) { + String pluginName = plugin.getName(); + if (pluginActions.containsKey(pluginName)) + throw new IllegalArgumentException("Plugin '" + pluginName + "' has already been loaded"); + pluginActions.put(pluginName, new HashSet<>()); + duplicates.clear(); + File directory = director.getFileManager().actionsDirectory(); + loadFiles(plugin, directory); + duplicates.forEach((key, value) -> plugin.getAnjoLogger() + .log("Duplicate Action: '" + key + "' (found " + value + " instances)")); + } + + public void unload(BlobPlugin plugin) { + String pluginName = plugin.getName(); + Set actions = pluginActions.get(pluginName); + if (actions == null) + return; + actions.forEach(actions::remove); + pluginActions.remove(pluginName); + } + + public static void unloadBlobPlugin(BlobPlugin plugin) { + BlobLib.getInstance().getMessageManager().unload(plugin); + } + + public static void loadBlobPlugin(BlobPlugin plugin, IManagerDirector director) { + ActionManager manager = BlobLib.getInstance().getActionManager(); + manager.load(plugin, director); + } + + public static void loadBlobPlugin(BlobPlugin plugin) { + loadBlobPlugin(plugin, plugin.getManagerDirector()); + } + + private void loadFiles(File path) { + File[] listOfFiles = path.listFiles(); + for (File file : listOfFiles) { + if (file.isFile()) { + if (file.getName().equals(".DS_Store")) + continue; + loadYamlConfiguration(file); + } + if (file.isDirectory()) + loadFiles(path); + } + } + + private void loadFiles(BlobPlugin plugin, File path) { + File[] listOfFiles = path.listFiles(); + for (File file : listOfFiles) { + if (file.isFile()) { + if (file.getName().equals(".DS_Store")) + continue; + loadYamlConfiguration(file); + } + if (file.isDirectory()) + loadFiles(path); + } + } + + private void loadYamlConfiguration(File file) { + YamlConfiguration yamlConfiguration = YamlConfiguration.loadConfiguration(file); + yamlConfiguration.getKeys(true).forEach(reference -> { + if (!yamlConfiguration.isConfigurationSection(reference)) + return; + ConfigurationSection section = yamlConfiguration.getConfigurationSection(reference); + if (!section.contains("Type") && !section.isString("Type")) + return; + if (actions.containsKey(reference)) { + addDuplicate(reference); + return; + } + actions.put(reference, Action.fromConfigurationSection(section)); + }); + } + + private void loadYamlConfiguration(File file, BlobPlugin plugin) { + YamlConfiguration yamlConfiguration = YamlConfiguration.loadConfiguration(file); + yamlConfiguration.getKeys(true).forEach(reference -> { + if (!yamlConfiguration.isConfigurationSection(reference)) + return; + ConfigurationSection section = yamlConfiguration.getConfigurationSection(reference); + if (!section.contains("Type") && !section.isString("Type")) + return; + if (actions.containsKey(reference)) { + addDuplicate(reference); + return; + } + actions.put(reference, Action.fromConfigurationSection(section)); + pluginActions.get(plugin.getName()).add(reference); + }); + } + + /** + * Loads a YamlConfiguration from a file and registers it to the plugin. + * Will return false if the plugin has not been loaded. + * Will print duplicates. + * NOTE: Printing duplicates runs for each time you call this method! + * + * @param file The file to load + * @param plugin The plugin to register the actions to + * @return Whether the plugin has been loaded + */ + public static boolean loadAndRegisterYamlConfiguration(File file, BlobPlugin plugin) { + ActionManager manager = BlobLib.getInstance().getActionManager(); + if (!manager.pluginActions.containsKey(plugin.getName())) + return false; + manager.loadYamlConfiguration(file, plugin); + manager.duplicates.forEach((key, value) -> BlobLib.getAnjoLogger() + .log("Duplicate Action: '" + key + "' (found " + value + " instances)")); + return true; + } + + private void addDuplicate(String key) { + if (duplicates.containsKey(key)) + duplicates.put(key, duplicates.get(key) + 1); + else + duplicates.put(key, 2); + } + + public Action getAction(String reference) { + return actions.get(reference); + } +} \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/managers/BlobLibConfigManager.java b/src/main/java/us/mytheria/bloblib/managers/BlobLibConfigManager.java new file mode 100644 index 00000000..20cac06c --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/managers/BlobLibConfigManager.java @@ -0,0 +1,50 @@ +package us.mytheria.bloblib.managers; + +import org.bukkit.configuration.file.FileConfiguration; +import us.mytheria.bloblib.BlobLib; +import us.mytheria.bloblib.entities.TinyEventListener; + +public class BlobLibConfigManager { + // Singleton + private static BlobLibConfigManager instance; + + public static BlobLibConfigManager getInstance(BlobLib director) { + if (instance == null) { + if (director == null) + throw new NullPointerException("injected dependency is null"); + BlobLibConfigManager.instance = new BlobLibConfigManager(director); + } + return instance; + } + + public static BlobLibConfigManager getInstance() { + return getInstance(null); + } + // Initium + + private final BlobLib plugin; + private TinyEventListener displayRiding; + private FileConfiguration configuration; + + private BlobLibConfigManager(BlobLib plugin) { + this.plugin = plugin; + reload(); + } + + public void reload() { + plugin.reloadConfig(); + plugin.saveDefaultConfig(); + plugin.getConfig().options().copyDefaults(true); + plugin.saveConfig(); + configuration = plugin.getConfig(); + displayRiding = TinyEventListener.READ(configuration, "Display-Unriding"); + } + + public FileConfiguration getConfiguration() { + return configuration; + } + + public TinyEventListener getDisplayRiding() { + return displayRiding; + } +} \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/managers/BlobLibFileManager.java b/src/main/java/us/mytheria/bloblib/managers/BlobLibFileManager.java index 263f9353..8933568a 100644 --- a/src/main/java/us/mytheria/bloblib/managers/BlobLibFileManager.java +++ b/src/main/java/us/mytheria/bloblib/managers/BlobLibFileManager.java @@ -19,9 +19,13 @@ public class BlobLibFileManager { private final File messages = new File(path.getPath() + "/BlobMessage"); private final File sounds = new File(path.getPath() + "/BlobSound"); private final File inventories = new File(path.getPath() + "/BlobInventory"); + private final File metaInventories = new File(path.getPath() + "/MetaBlobInventory"); + private final File actions = new File(path.getPath() + "/Action"); private final File defaultSounds = new File(sounds.getPath() + "/bloblib_sounds.yml"); private final File defaultMessages = new File(messages.getPath() + "/bloblib_lang.yml"); private final File defaultInventories = new File(inventories.getPath() + "/bloblib_inventories.yml"); + private final File defaultMetaInventories = new File(metaInventories.getPath() + "/bloblib_meta_inventories.yml"); + private final File defaultActions = new File(actions.getPath() + "/bloblib_actions.yml"); /** * Will create a new BlobLibFileManager instance @@ -40,13 +44,19 @@ public void loadFiles() { if (!messages.exists()) messages.mkdir(); if (!sounds.exists()) sounds.mkdir(); if (!inventories.exists()) inventories.mkdir(); + if (!metaInventories.exists()) metaInventories.mkdir(); + if (!actions.exists()) actions.mkdir(); /////////////////////////////////////////// if (!defaultSounds.exists()) defaultSounds.createNewFile(); if (!defaultMessages.exists()) defaultMessages.createNewFile(); if (!defaultInventories.exists()) defaultInventories.createNewFile(); + if (!defaultMetaInventories.exists()) defaultMetaInventories.createNewFile(); + if (!defaultActions.exists()) defaultActions.createNewFile(); ResourceUtil.updateYml(sounds, "/tempbloblib_sounds.yml", "bloblib_sounds.yml", defaultSounds, plugin); ResourceUtil.updateYml(messages, "/tempbloblib_lang.yml", "bloblib_lang.yml", defaultMessages, plugin); ResourceUtil.updateYml(inventories, "/tempInventories.yml", "bloblib_inventories.yml", defaultInventories, plugin); + ResourceUtil.updateYml(metaInventories, "/tempMetaInventories.yml", "bloblib_meta_inventories.yml", defaultMetaInventories, plugin); + ResourceUtil.updateYml(actions, "/tempActions.yml", "bloblib_actions.yml", defaultActions, plugin); } catch (Exception e) { e.printStackTrace(); } @@ -82,6 +92,15 @@ public File messagesDirectory() { return messages; } + /** + * Will return actions directory (INSIDE BlobLib PLUGIN DIRECTORY) + * + * @return The actions directory + */ + public File actionsDirectory() { + return actions; + } + /** * Will return sounds directory (INSIDE BlobLib PLUGIN DIRECTORY) * @@ -123,6 +142,15 @@ public File inventoriesDirectory() { return inventories; } + /** + * Will return meta inventories' directory (INSIDE BlobLib PLUGIN DIRECTORY) + * + * @return The meta inventories directory + */ + public File metaInventoriesDirectory() { + return metaInventories; + } + /** * Will return the default inventories file * @@ -132,6 +160,15 @@ public File defaultInventoriesFile() { return defaultInventories; } + /** + * Will return the default meta inventories file + * + * @return The default meta inventories file + */ + public File defaultMetaInventoriesFile() { + return defaultMetaInventories; + } + /** * Unpacks an embedded file from the plugin's jar's resources folder. * If softUpdate is true, it will only generate the file if it doesn't diff --git a/src/main/java/us/mytheria/bloblib/managers/BlobLibListenerManager.java b/src/main/java/us/mytheria/bloblib/managers/BlobLibListenerManager.java new file mode 100644 index 00000000..8f24f640 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/managers/BlobLibListenerManager.java @@ -0,0 +1,32 @@ +package us.mytheria.bloblib.managers; + +import us.mytheria.bloblib.listeners.DisplayUnriding; + +public class BlobLibListenerManager { + // Singleton + private static BlobLibListenerManager instance; + + public static BlobLibListenerManager getInstance(BlobLibConfigManager configManager) { + if (instance == null) { + if (configManager == null) + throw new NullPointerException("injected dependency is null"); + BlobLibListenerManager.instance = new BlobLibListenerManager(configManager); + } + return instance; + } + + public static BlobLibListenerManager getInstance() { + return getInstance(null); + } + // Initium + + private final DisplayUnriding displayUnriding; + + private BlobLibListenerManager(BlobLibConfigManager configManager) { + this.displayUnriding = new DisplayUnriding(configManager); + } + + public void reload() { + displayUnriding.reload(); + } +} \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/managers/BlobPlugin.java b/src/main/java/us/mytheria/bloblib/managers/BlobPlugin.java index ffd80f49..901f7e5e 100644 --- a/src/main/java/us/mytheria/bloblib/managers/BlobPlugin.java +++ b/src/main/java/us/mytheria/bloblib/managers/BlobPlugin.java @@ -2,6 +2,10 @@ import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import us.mytheria.bloblib.entities.ConfigDecorator; +import us.mytheria.bloblib.entities.GitHubPluginUpdater; +import us.mytheria.bloblib.entities.PluginUpdater; import us.mytheria.bloblib.entities.logger.BlobPluginLogger; /** @@ -71,10 +75,41 @@ public void unregisterFromBlobLib() { * * @return The ManagerDirector of the plugin. */ - public abstract ManagerDirector getManagerDirector(); + public abstract IManagerDirector getManagerDirector(); + + /** + * Gets the PluginUpdater of this BlobPlugin. + * If null, it means the plugin does not have an updater. + * + * @return The PluginUpdater of this BlobPlugin. + */ + @Nullable + public PluginUpdater getPluginUpdater() { + return null; + } @NotNull public BlobPluginLogger getAnjoLogger() { return logger; } + + /** + * Will generate an updater for this BlobPlugin + * + * @param repositoryOwner The owner of GitHub repository + * @param repository The repository name + * @return The updater + */ + public GitHubPluginUpdater generateGitHubUpdater(String repositoryOwner, String repository) { + return new GitHubPluginUpdater(this, repositoryOwner, repository); + } + + /** + * Will get a ConfigDecorator for this BlobPlugin + * + * @return The ConfigDecorator + */ + public ConfigDecorator getConfigDecorator() { + return new ConfigDecorator(this); + } } diff --git a/src/main/java/us/mytheria/bloblib/managers/ChatListenerManager.java b/src/main/java/us/mytheria/bloblib/managers/ChatListenerManager.java index ef1b234d..3477d616 100644 --- a/src/main/java/us/mytheria/bloblib/managers/ChatListenerManager.java +++ b/src/main/java/us/mytheria/bloblib/managers/ChatListenerManager.java @@ -24,9 +24,9 @@ public ChatListenerManager() { @EventHandler public void onChat(AsyncPlayerChatEvent e) { - if (!chatListeners.containsKey(e.getPlayer().getName())) - return; ChatListener listener = chatListeners.get(e.getPlayer().getName()); + if (listener == null) + return; listener.setInput(e.getMessage()); e.setCancelled(true); } diff --git a/src/main/java/us/mytheria/bloblib/managers/DropListenerManager.java b/src/main/java/us/mytheria/bloblib/managers/DropListenerManager.java index 6e1013f0..dc3dadbc 100644 --- a/src/main/java/us/mytheria/bloblib/managers/DropListenerManager.java +++ b/src/main/java/us/mytheria/bloblib/managers/DropListenerManager.java @@ -25,9 +25,9 @@ public DropListenerManager() { @EventHandler public void onDrop(PlayerDropItemEvent e) { String name = e.getPlayer().getName(); - if (!dropListeners.containsKey(name)) - return; DropListener listener = dropListeners.get(name); + if (listener == null) + return; listener.setInput(e.getItemDrop().getItemStack()); e.setCancelled(true); } diff --git a/src/main/java/us/mytheria/bloblib/managers/IManagerDirector.java b/src/main/java/us/mytheria/bloblib/managers/IManagerDirector.java new file mode 100644 index 00000000..0a20fc94 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/managers/IManagerDirector.java @@ -0,0 +1,31 @@ +package us.mytheria.bloblib.managers; + +import us.mytheria.bloblib.entities.IFileManager; + +public interface IManagerDirector { + /** + * Will do unload/disable logic (synchronously). + */ + void unload(); + + /** + * Will do logic once the main world(s) are loaded (synchronously) + */ + void postWorld(); + + /** + * Will do reload logic (asynchronously). + */ + void reload(); + + /** + * Will get the file manager that's required by BlobLib. + * If proxied, operations done on the file manager will + * be done when loading the manager director, which + * will not affect gameplay since this should be done + * on server start or on test servers (if reloading). + * + * @return the file manager. + */ + IFileManager getFileManager(); +} diff --git a/src/main/java/us/mytheria/bloblib/managers/InventoryManager.java b/src/main/java/us/mytheria/bloblib/managers/InventoryManager.java index e9d6d379..7135372a 100644 --- a/src/main/java/us/mytheria/bloblib/managers/InventoryManager.java +++ b/src/main/java/us/mytheria/bloblib/managers/InventoryManager.java @@ -4,18 +4,23 @@ import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.YamlConfiguration; import us.mytheria.bloblib.BlobLib; -import us.mytheria.bloblib.entities.inventory.BlobInventory; +import us.mytheria.bloblib.entities.IFileManager; +import us.mytheria.bloblib.entities.inventory.*; import javax.annotation.Nullable; import java.io.File; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.Set; public class InventoryManager { private final BlobLib main; - private HashMap inventories; - private HashMap> pluginInventories; + private HashMap> blobInventories; + private HashMap> metaInventories; + private HashMap metaInventoriesShards; + private HashMap> pluginBlobInventories; + private HashMap> pluginMetaInventories; private HashMap duplicates; public InventoryManager() { @@ -27,27 +32,35 @@ public void reload() { } public void load() { - inventories = new HashMap<>(); - pluginInventories = new HashMap<>(); + metaInventoriesShards = new HashMap<>(); + blobInventories = new HashMap<>(); + metaInventories = new HashMap<>(); + pluginBlobInventories = new HashMap<>(); + pluginMetaInventories = new HashMap<>(); duplicates = new HashMap<>(); - loadFiles(main.getFileManager().inventoriesDirectory()); + loadBlobInventories(main.getFileManager().inventoriesDirectory()); + loadMetaInventories(main.getFileManager().metaInventoriesDirectory()); duplicates.forEach((key, value) -> BlobLib.getAnjoLogger() - .log("Duplicate BlobInventory: '" + key + "' (found " + value + " instances)")); + .log("Duplicate Inventory: '" + key + "' (found " + value + " instances)")); } - public void load(BlobPlugin plugin, ManagerDirector director) { + public void load(BlobPlugin plugin, IManagerDirector director) { String pluginName = plugin.getName(); - if (pluginInventories.containsKey(pluginName)) + if (pluginBlobInventories.containsKey(pluginName)) throw new IllegalArgumentException("Plugin '" + pluginName + "' has already been loaded"); - pluginInventories.put(pluginName, new HashSet<>()); + pluginBlobInventories.put(pluginName, new HashSet<>()); + pluginMetaInventories.put(pluginName, new HashSet<>()); duplicates.clear(); - File directory = director.getFileManager().inventoriesDirectory(); - loadFiles(plugin, directory); + IFileManager fileManager = director.getFileManager(); + File blobDirectory = fileManager.inventoriesDirectory(); + loadBlobInventories(plugin, blobDirectory); + File metaDirectory = fileManager.metaInventoriesDirectory(); + loadMetaInventories(plugin, metaDirectory); duplicates.forEach((key, value) -> plugin.getAnjoLogger() - .log("Duplicate BlobInventory: '" + key + "' (found " + value + " instances)")); + .log("Duplicate Inventory: '" + key + "' (found " + value + " instances)")); } - public static void loadBlobPlugin(BlobPlugin plugin, ManagerDirector director) { + public static void loadBlobPlugin(BlobPlugin plugin, IManagerDirector director) { InventoryManager manager = BlobLib.getInstance().getInventoryManager(); manager.load(plugin, director); } @@ -58,10 +71,24 @@ public static void loadBlobPlugin(BlobPlugin plugin) { public void unload(BlobPlugin plugin) { String pluginName = plugin.getName(); - if (!pluginInventories.containsKey(pluginName)) + Set blobInventories = pluginBlobInventories.get(pluginName); + if (blobInventories == null) return; - pluginInventories.get(pluginName).forEach(inventories::remove); - pluginInventories.remove(pluginName); + Iterator iterator = blobInventories.iterator(); + while (iterator.hasNext()) { + String inventoryName = iterator.next(); + this.blobInventories.remove(inventoryName); + iterator.remove(); + } + Set metaInventoryKeys = pluginMetaInventories.get(pluginName); + iterator = metaInventoryKeys.iterator(); + while (iterator.hasNext()) { + String inventoryName = iterator.next(); + this.metaInventories.remove(inventoryName); + iterator.remove(); + } + pluginBlobInventories.remove(pluginName); + pluginMetaInventories.remove(pluginName); } public static void unloadBlobPlugin(BlobPlugin plugin) { @@ -69,42 +96,69 @@ public static void unloadBlobPlugin(BlobPlugin plugin) { manager.unload(plugin); } - private void loadFiles(File path) { + private void loadBlobInventories(File path) { + File[] listOfFiles = path.listFiles(); + for (File file : listOfFiles) { + if (file.isFile()) { + if (!file.getName().endsWith(".yml")) + continue; + loadBlobInventory(file); + } + if (file.isDirectory()) + loadBlobInventories(path); + } + } + + private void loadMetaInventories(File path) { File[] listOfFiles = path.listFiles(); for (File file : listOfFiles) { if (file.isFile()) { - if (file.getName().equals(".DS_Store")) + if (!file.getName().endsWith(".yml")) continue; - loadYamlConfiguration(file); + loadMetaInventory(file); } if (file.isDirectory()) - loadFiles(path); + loadMetaInventories(path); } } - private void loadFiles(BlobPlugin plugin, File path) { + private void loadBlobInventories(BlobPlugin plugin, File path) { File[] listOfFiles = path.listFiles(); for (File file : listOfFiles) { if (file.isFile()) { - if (file.getName().equals(".DS_Store")) + if (!file.getName().endsWith(".yml")) continue; - loadYamlConfiguration(plugin, file); + loadBlobInventory(plugin, file); } if (file.isDirectory()) - loadFiles(plugin, path); + loadBlobInventories(plugin, path); } } - private void loadYamlConfiguration(BlobPlugin plugin, File file) { + private void loadMetaInventories(BlobPlugin plugin, File path) { + File[] listOfFiles = path.listFiles(); + for (File file : listOfFiles) { + if (file.isFile()) { + if (!file.getName().endsWith(".yml")) + continue; + loadMetaInventory(plugin, file); + } + if (file.isDirectory()) + loadMetaInventory(plugin, path); + } + } + + private void loadBlobInventory(BlobPlugin plugin, File file) { String fileName = FilenameUtils.removeExtension(file.getName()); YamlConfiguration yamlConfiguration = YamlConfiguration.loadConfiguration(file); if (yamlConfiguration.contains("Size") && yamlConfiguration.isInt("Size")) { - if (inventories.containsKey(fileName)) { + if (blobInventories.containsKey(fileName)) { addDuplicate(fileName); return; } - add(fileName, BlobInventory.fromConfigurationSection(yamlConfiguration)); - pluginInventories.get(plugin.getName()).add(fileName); + addBlobInventory(fileName, InventoryBuilderCarrier. + BLOB_FROM_CONFIGURATION_SECTION(yamlConfiguration, fileName)); + pluginBlobInventories.get(plugin.getName()).add(fileName); return; } yamlConfiguration.getKeys(true).forEach(reference -> { @@ -113,47 +167,102 @@ private void loadYamlConfiguration(BlobPlugin plugin, File file) { ConfigurationSection section = yamlConfiguration.getConfigurationSection(reference); if (!section.contains("Size") && !section.isInt("Size")) return; - if (inventories.containsKey(reference)) { + if (blobInventories.containsKey(reference)) { addDuplicate(reference); return; } - add(reference, BlobInventory.fromConfigurationSection(section)); - pluginInventories.get(plugin.getName()).add(reference); + addBlobInventory(reference, InventoryBuilderCarrier. + BLOB_FROM_CONFIGURATION_SECTION(section, reference)); + pluginBlobInventories.get(plugin.getName()).add(reference); }); } - public static void continueLoading(BlobPlugin plugin, boolean warnDuplicates, File... files) { + private void loadMetaInventory(BlobPlugin plugin, File file) { + String fileName = FilenameUtils.removeExtension(file.getName()); + YamlConfiguration yamlConfiguration = YamlConfiguration.loadConfiguration(file); + if (yamlConfiguration.contains("Size") && yamlConfiguration.isInt("Size")) { + if (metaInventories.containsKey(fileName)) { + addDuplicate(fileName); + return; + } + addMetaInventory(fileName, InventoryBuilderCarrier. + META_FROM_CONFIGURATION_SECTION(yamlConfiguration, fileName)); + pluginMetaInventories.get(plugin.getName()).add(fileName); + return; + } + yamlConfiguration.getKeys(true).forEach(reference -> { + if (!yamlConfiguration.isConfigurationSection(reference)) + return; + ConfigurationSection section = yamlConfiguration.getConfigurationSection(reference); + if (!section.contains("Size") && !section.isInt("Size")) + return; + if (metaInventories.containsKey(reference)) { + addDuplicate(reference); + return; + } + addMetaInventory(reference, InventoryBuilderCarrier. + META_FROM_CONFIGURATION_SECTION(section, reference)); + pluginMetaInventories.get(plugin.getName()).add(reference); + }); + } + + public static void continueLoadingBlobInventories(BlobPlugin plugin, boolean warnDuplicates, File... files) { InventoryManager manager = BlobLib.getInstance().getInventoryManager(); manager.duplicates.clear(); for (File file : files) - manager.loadYamlConfiguration(plugin, file); + manager.loadBlobInventory(plugin, file); if (warnDuplicates) manager.duplicates.forEach((key, value) -> plugin.getAnjoLogger() .log("Duplicate BlobInventory: '" + key + "' (found " + value + " instances)")); } - public static void continueLoading(BlobPlugin plugin, File... files) { - continueLoading(plugin, true, files); + public static void continueLoadingBlobInventories(BlobPlugin plugin, File... files) { + continueLoadingBlobInventories(plugin, true, files); } - /** - * @param plugin The plugin that is loading the inventory - * @param file The file to load - * @deprecated Use {@link #continueLoading(BlobPlugin, File...)} instead - */ - @Deprecated - public static void loadAndRegisterYamlConfiguration(BlobPlugin plugin, File file) { + public static void continueLoadingMetaInventories(BlobPlugin plugin, boolean warnDuplicates, File... files) { InventoryManager manager = BlobLib.getInstance().getInventoryManager(); - manager.loadYamlConfiguration(plugin, file); - manager.duplicates.forEach((key, value) -> BlobLib.getAnjoLogger() - .log("Duplicate BlobInventory: '" + key + "' (found " + value + " instances)")); + manager.duplicates.clear(); + for (File file : files) + manager.loadMetaInventory(plugin, file); + if (warnDuplicates) + manager.duplicates.forEach((key, value) -> plugin.getAnjoLogger() + .log("Duplicate MetaBlobInventory: '" + key + "' (found " + value + " instances)")); + } + + public static void continueLoadingMetaInventories(BlobPlugin plugin, File... files) { + continueLoadingMetaInventories(plugin, true, files); + } + + private void loadBlobInventory(File file) { + String fileName = FilenameUtils.removeExtension(file.getName()); + YamlConfiguration yamlConfiguration = YamlConfiguration.loadConfiguration(file); + if (yamlConfiguration.contains("Size") && yamlConfiguration.isInt("Size")) { + addBlobInventory(fileName, InventoryBuilderCarrier. + BLOB_FROM_CONFIGURATION_SECTION(yamlConfiguration, fileName)); + return; + } + yamlConfiguration.getKeys(true).forEach(reference -> { + if (!yamlConfiguration.isConfigurationSection(reference)) + return; + ConfigurationSection section = yamlConfiguration.getConfigurationSection(reference); + if (!section.contains("Size") && !section.isInt("Size")) + return; + if (blobInventories.containsKey(reference)) { + addDuplicate(reference); + return; + } + addBlobInventory(reference, InventoryBuilderCarrier. + BLOB_FROM_CONFIGURATION_SECTION(section, reference)); + }); } - private void loadYamlConfiguration(File file) { + private void loadMetaInventory(File file) { String fileName = FilenameUtils.removeExtension(file.getName()); YamlConfiguration yamlConfiguration = YamlConfiguration.loadConfiguration(file); if (yamlConfiguration.contains("Size") && yamlConfiguration.isInt("Size")) { - add(fileName, BlobInventory.fromConfigurationSection(yamlConfiguration)); + addMetaInventory(fileName, InventoryBuilderCarrier. + META_FROM_CONFIGURATION_SECTION(yamlConfiguration, fileName)); return; } yamlConfiguration.getKeys(true).forEach(reference -> { @@ -162,14 +271,16 @@ private void loadYamlConfiguration(File file) { ConfigurationSection section = yamlConfiguration.getConfigurationSection(reference); if (!section.contains("Size") && !section.isInt("Size")) return; - if (inventories.containsKey(reference)) { + if (blobInventories.containsKey(reference)) { addDuplicate(reference); return; } - add(reference, BlobInventory.fromConfigurationSection(section)); + addMetaInventory(reference, InventoryBuilderCarrier. + META_FROM_CONFIGURATION_SECTION(section, reference)); }); } + private void addDuplicate(String key) { if (duplicates.containsKey(key)) duplicates.put(key, duplicates.get(key) + 1); @@ -177,20 +288,60 @@ private void addDuplicate(String key) { duplicates.put(key, 2); } + @Nullable + public InventoryBuilderCarrier getInventoryBuilderCarrier(String key) { + return blobInventories.get(key); + } + @Nullable public BlobInventory getInventory(String key) { - return inventories.get(key); + InventoryBuilderCarrier carrier = getInventoryBuilderCarrier(key); + if (carrier == null) + return null; + return BlobInventory.fromInventoryBuilderCarrier(carrier); } @Nullable public BlobInventory cloneInventory(String key) { - BlobInventory inventory = inventories.get(key); + BlobInventory inventory = getInventory(key); if (inventory == null) return null; return inventory.copy(); } - private void add(String key, BlobInventory inventory) { - inventories.put(key, inventory); + @Nullable + public InventoryBuilderCarrier getMetaInventoryBuilderCarrier(String key) { + return metaInventories.get(key); + } + + @Nullable + public MetaBlobInventory getMetaInventory(String key) { + InventoryBuilderCarrier carrier = getMetaInventoryBuilderCarrier(key); + if (carrier == null) + return null; + return MetaBlobInventory.fromInventoryBuilderCarrier(carrier); + } + + @Nullable + public MetaBlobInventory cloneMetaInventory(String key) { + MetaBlobInventory inventory = getMetaInventory(key); + if (inventory == null) + return null; + return inventory.copy(); + } + + @Nullable + public MetaInventoryShard getMetaInventoryShard(String type) { + return metaInventoriesShards.get(type); + } + + private void addBlobInventory(String key, InventoryBuilderCarrier inventory) { + blobInventories.put(key, inventory); + } + + private void addMetaInventory(String key, InventoryBuilderCarrier inventory) { + metaInventories.put(key, inventory); + metaInventoriesShards.computeIfAbsent(inventory.type(), type -> new MetaInventoryShard()) + .addInventory(inventory, key); } } diff --git a/src/main/java/us/mytheria/bloblib/managers/Manager.java b/src/main/java/us/mytheria/bloblib/managers/Manager.java index be92ba86..fde3e5c0 100644 --- a/src/main/java/us/mytheria/bloblib/managers/Manager.java +++ b/src/main/java/us/mytheria/bloblib/managers/Manager.java @@ -1,16 +1,10 @@ package us.mytheria.bloblib.managers; public abstract class Manager { - private final BlobPlugin plugin; private final ManagerDirector managerDirector; public Manager(ManagerDirector managerDirector) { this.managerDirector = managerDirector; - this.plugin = managerDirector.getPlugin(); - loadInConstructor(); - } - - public void loadInConstructor() { } public void loadManually() { @@ -30,6 +24,6 @@ public ManagerDirector getManagerDirector() { } public BlobPlugin getPlugin() { - return plugin; + return managerDirector.getPlugin(); } } diff --git a/src/main/java/us/mytheria/bloblib/managers/ManagerDirector.java b/src/main/java/us/mytheria/bloblib/managers/ManagerDirector.java index 689e1d26..8e5fd394 100644 --- a/src/main/java/us/mytheria/bloblib/managers/ManagerDirector.java +++ b/src/main/java/us/mytheria/bloblib/managers/ManagerDirector.java @@ -1,17 +1,28 @@ package us.mytheria.bloblib.managers; import me.anjoismysign.anjo.logger.Logger; +import org.apache.commons.io.FileUtils; +import org.bukkit.NamespacedKey; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; import us.mytheria.bloblib.BlobLib; -import us.mytheria.bloblib.entities.BlobFileManager; +import us.mytheria.bloblib.entities.*; +import us.mytheria.bloblib.entities.currency.Currency; +import us.mytheria.bloblib.entities.currency.EconomyFactory; +import us.mytheria.bloblib.entities.currency.WalletOwner; +import us.mytheria.bloblib.entities.currency.WalletOwnerManager; +import us.mytheria.bloblib.entities.proxy.BlobProxifier; import us.mytheria.bloblib.utilities.ResourceUtil; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.Set; +import java.util.function.Function; -public abstract class ManagerDirector { +public abstract class ManagerDirector implements IManagerDirector { private final BlobPlugin plugin; private final HashMap managers; private final ChatListenerManager chatListenerManager; @@ -19,6 +30,9 @@ public abstract class ManagerDirector { private final SelPosListenerManager positionListenerManager; private final DropListenerManager dropListenerManager; private final BlobFileManager blobFileManager; + private final BukkitPluginOperator pluginOperator; + private final IFileManager proxiedFileManager; + private final Map namespacedKeys; /** * Constructs a new ManagerDirector. @@ -26,8 +40,15 @@ public abstract class ManagerDirector { * @param plugin The BlobPlugin */ public ManagerDirector(BlobPlugin plugin) { + this.namespacedKeys = new HashMap<>(); + namespacedKeys.put("tangibleCurrencyKey", new NamespacedKey(plugin, "tangibleCurrencyKey")); + namespacedKeys.put("tangibleCurrencyDenomination", new NamespacedKey(plugin, "tangibleCurrencyDenomination")); this.plugin = plugin; - this.blobFileManager = new BlobFileManager(this, "plugins/" + plugin.getName()); + this.pluginOperator = () -> plugin; + this.blobFileManager = new BlobFileManager(this, + "plugins/" + plugin.getName(), + plugin); + this.proxiedFileManager = BlobProxifier.PROXY(blobFileManager); chatListenerManager = BlobLib.getInstance().getChatManager(); selectorListenerManager = BlobLib.getInstance().getSelectorManager(); positionListenerManager = BlobLib.getInstance().getPositionManager(); @@ -53,8 +74,14 @@ public ManagerDirector(BlobPlugin plugin) { */ @Deprecated public ManagerDirector(BlobPlugin plugin, String fileManagerPathname) { + this.namespacedKeys = new HashMap<>(); + namespacedKeys.put("tangibleCurrencyKey", new NamespacedKey(plugin, "tangibleCurrencyKey")); + namespacedKeys.put("tangibleCurrencyDenomination", new NamespacedKey(plugin, "tangibleCurrencyDenomination")); this.plugin = plugin; - this.blobFileManager = new BlobFileManager(this, fileManagerPathname); + this.pluginOperator = () -> plugin; + this.blobFileManager = new BlobFileManager(this, + fileManagerPathname, plugin); + this.proxiedFileManager = BlobProxifier.PROXY(blobFileManager); chatListenerManager = BlobLib.getInstance().getChatManager(); selectorListenerManager = BlobLib.getInstance().getSelectorManager(); positionListenerManager = BlobLib.getInstance().getPositionManager(); @@ -70,8 +97,13 @@ public ManagerDirector(BlobPlugin plugin, String fileManagerPathname) { * @param fileManager The file manager */ public ManagerDirector(BlobPlugin plugin, BlobFileManager fileManager) { + this.namespacedKeys = new HashMap<>(); + namespacedKeys.put("tangibleCurrencyKey", new NamespacedKey(plugin, "tangibleCurrencyKey")); + namespacedKeys.put("tangibleCurrencyDenomination", new NamespacedKey(plugin, "tangibleCurrencyDenomination")); this.plugin = plugin; - this.blobFileManager = fileManager; + this.pluginOperator = () -> plugin; + this.blobFileManager = Objects.requireNonNull(fileManager, "BlobFileManager cannot be null!"); + this.proxiedFileManager = BlobProxifier.PROXY(blobFileManager); chatListenerManager = BlobLib.getInstance().getChatManager(); selectorListenerManager = BlobLib.getInstance().getSelectorManager(); positionListenerManager = BlobLib.getInstance().getPositionManager(); @@ -80,10 +112,181 @@ public ManagerDirector(BlobPlugin plugin, BlobFileManager fileManager) { plugin.registerToBlobLib(this); } + public NamespacedKey getNamespacedKey(String key) { + return namespacedKeys.get(key); + } + + /** + * Will proxy this ManagerDirector to a new instance of ManagerDirector. + * + * @return The proxied ManagerDirector + */ + public IManagerDirector proxy() { + return BlobProxifier.PROXY(this); + } + + /** + * Will get the BukkitPluginOperator of the plugin. + * + * @return The BukkitPluginOperator + */ + public BukkitPluginOperator getPluginOperator() { + return pluginOperator; + } + + /** + * Adds a manager to the director. + * + * @param key The key of the manager + * @param manager The manager + */ public void addManager(String key, Manager manager) { managers.put(key, manager); } + /** + * Adds an object director to the director. + * + * @param objectName The name of the object + * @param readFunction The function that reads the file + * @param hasObjectBuilderManager Whether the object director will implement object builder manager + * @param The type of the object + */ + public void addDirector(String objectName, + Function readFunction, + boolean hasObjectBuilderManager) { + ObjectDirectorData directorData = + ObjectDirectorData.simple(getRealFileManager(), objectName); + addManager(objectName + "Director", + new ObjectDirector<>(this, + directorData, readFunction, hasObjectBuilderManager)); + } + + /** + * Adds a currency director to the director. + * + * @param objectName The name of the object + */ + public void addCurrencyDirector(String objectName) { + addManager(objectName + "Director", + EconomyFactory.CURRENCY_DIRECTOR(this, objectName)); + } + + /** + * Adds a BlobSerializableManager to the director + * that does not listen to events. + * + * @param key The key of the manager + * @param generator The generator function + * @param crudableName The name of the crudable + * @param logActivity Whether to log activity + * @param The type of the BlobSerializable + */ + public void addSimpleBlobSerializableManager(String key, + Function generator, + String crudableName, + boolean logActivity) { + addManager(key, BlobSerializableManagerFactory.SIMPLE(this, + generator, crudableName, logActivity)); + } + + /** + * Adds a BlobSerializableManager to the director + * that listens to events. + * + * @param key The key of the manager + * @param generator The generator function + * @param crudableName The name of the crudable + * @param logActivity Whether to log activity + * @param joinEvent The join event. + * Function consumes the BlobSerializable + * related in the event and needs to return + * the event to be called. + * @param quitEvent The quit event. + * Function consumes the BlobSerializable + * related in the event and needs to return + * the event to be called. + * @param The type of the blob serializable + */ + public void addListenerBlobSerializableManager(String key, + Function generator, + String crudableName, + boolean logActivity, + @Nullable Function joinEvent, + @Nullable Function quitEvent) { + addManager(key, BlobSerializableManagerFactory.LISTENER(this, + generator, crudableName, logActivity, joinEvent, quitEvent)); + } + + /** + * Adds a wallet owner manager to the director. + * + * @param key The key of the manager + * @param newBorn A function that by passing a UUID, it will fill a BlobCrudable + * with default key-value pairs. + * This is used to create new/fresh WalletOwners. + * @param walletOwner A function that by passing a BlobCrudable, it will return a WalletOwner. + * WalletOwners use this to store their data inside databases. + * @param crudableName The name of the BlobCrudable. This will be used for + * as the column name in the database. + * @param logActivity Whether to log activity in the console. + * @param joinEvent A function that by passing a WalletOwner, it will return a join event. + * It's called SYNCHRONOUSLY. + * It's called when a player joins the server. + * @param quitEvent A function that by passing a WalletOwner, it will return a quit event. + * It's called SYNCHRONOUSLY. + * It's called when a player quits/leaves the server. + * @param The type of WalletOwner. + */ + public void addWalletOwnerManager(String key, + Function newBorn, + Function walletOwner, + String crudableName, boolean logActivity, + Function joinEvent, + Function quitEvent) { + addManager(key, + EconomyFactory.WALLET_OWNER_MANAGER(this, + newBorn, walletOwner, crudableName, logActivity, joinEvent, quitEvent)); + } + + /** + * Adds a wallet owner manager to the director. + * This is a simplified version {@link EconomyFactory#SIMPLE_WALLET_OWNER_MANAGER(ManagerDirector, Function, Function, String, boolean)} + * No events are registered for join and quit actions. + * + * @param key The key of the manager + * @param newBorn A function that by passing a UUID, it will fill a BlobCrudable + * with default key-value pairs. + * This is used to create new/fresh WalletOwners. + * @param walletOwner A function that by passing a BlobCrudable, it will return a WalletOwner. + * WalletOwners use this to store their data inside databases. + * @param crudableName The name of the BlobCrudable. This will be used for + * as the column name in the database. + * @param logActivity Whether to log activity in the console. + * @param The type of WalletOwner. + */ + public void addSimpleWalletOwnerManager(String key, + Function newBorn, + Function walletOwner, + String crudableName, boolean logActivity) { + addManager(key, + EconomyFactory.SIMPLE_WALLET_OWNER_MANAGER(this, + newBorn, walletOwner, crudableName, logActivity)); + } + + /** + * Adds an object director to the director. + * This method assumes that the object director will implement object builder manager. + * + * @param objectName The name of the object + * @param readFunction The function that reads the file + * @param The type of the object + */ + public void addDirector(String objectName, + Function readFunction) { + addDirector(objectName, readFunction, true); + } + /** * Logic that should run whenever reloading the plugin. */ @@ -100,6 +303,71 @@ public Manager getManager(String key) { return managers.get(key); } + /** + * Will retrieve a manager by providing a String 'key'. + * + * @param key the key of the manager + * @param clazz The class of the object + * @param The type of the object + * @return The Manager that corresponds to the key + */ + @SuppressWarnings("unchecked") + public T getManager(String key, Class clazz) { + return (T) getManager(key); + } + + /** + * Will retrieve an object director by providing a String 'key'. + * The key must end with 'Director' + * + * @param key the key of the manager + * @param clazz The class of the object + * @param The type of the object + * @return The ObjectDirector that corresponds to the key + */ + @SuppressWarnings("unchecked") + public ObjectDirector getDirector(String key, + Class clazz) { + return (ObjectDirector) getManager(key + "Director"); + } + + /** + * Will retrieve a wallet owner manager by providing a String 'key'. + * + * @param key the key of the manager + * @param clazz The class of the object + * @param The type of the object (needs to implement WalletOwner) + * @return The WalletOwnerManager that corresponds to the key + */ + @SuppressWarnings("unchecked") + public WalletOwnerManager getWalletOwnerManager(String key, + Class clazz) { + return (WalletOwnerManager) getManager(key); + } + + /** + * Will retrieve a blob serializable manager by providing a String 'key'. + * + * @param key the key of the manager + * @param clazz The class of the object + * @param The type of the object (needs to implement BlobSerializable) + * @return The BlobSerializableManager that corresponds to the key + */ + @SuppressWarnings("unchecked") + public BlobSerializableManager getBlobSerializableManager(String key, Class clazz) { + return (BlobSerializableManager) getManager(key); + } + + /** + * Will retrieve a currency director by providing a String 'objectName'. + * + * @param objectName The name of the object + * @return The ObjectDirector that corresponds to the key + */ + public ObjectDirector getCurrencyDirector(String objectName) { + return getDirector(objectName, Currency.class); + } + /** * Logic that should run when plugin is unloading. */ @@ -140,10 +408,14 @@ public DropListenerManager getDropListenerManager() { return dropListenerManager; } + public IFileManager getFileManager() { + return proxiedFileManager; + } + /** * @return The BlobFileManager that the director manages. */ - public BlobFileManager getFileManager() { + public BlobFileManager getRealFileManager() { return blobFileManager; } @@ -164,7 +436,7 @@ public BlobPlugin getPlugin() { */ public ManagerDirector detachMessageAsset(String fileName, boolean debug) { Logger logger = getPlugin().getAnjoLogger(); - File path = getFileManager().messagesDirectory(); + File path = getRealFileManager().messagesDirectory(); File file = new File(path + "/" + fileName + ".yml"); if (!file.exists()) { try { @@ -205,7 +477,7 @@ public ManagerDirector detachMessageAsset(String fileName) { */ public ManagerDirector detachSoundAsset(String fileName, boolean debug) { Logger logger = getPlugin().getAnjoLogger(); - File path = getFileManager().soundsDirectory(); + File path = getRealFileManager().soundsDirectory(); File file = new File(path + "/" + fileName + ".yml"); if (!file.exists()) { try { @@ -244,11 +516,12 @@ public ManagerDirector detachSoundAsset(String fileName) { * @param debug Whether to print debug messages * @return The ManagerDirector instance for method chaining */ - public ManagerDirector registerAndUpdateInventoryAsset(String fileName, boolean debug) { - File path = getFileManager().inventoriesDirectory(); + public ManagerDirector registerAndUpdateBlobInventory(String fileName, boolean debug) { + File path = getRealFileManager().inventoriesDirectory(); File file = new File(path + "/" + fileName + ".yml"); - blobFileManager.updateYAML(file); - InventoryManager.continueLoading(plugin, file); + if (!blobFileManager.updateYAML(file)) + return this; + InventoryManager.continueLoadingBlobInventories(plugin, file); if (debug) getPlugin().getAnjoLogger().debug(" inventory asset " + fileName + ".yml successfully registered"); return this; @@ -262,8 +535,94 @@ public ManagerDirector registerAndUpdateInventoryAsset(String fileName, boolean * @param fileName The name of the file to detach * @return The ManagerDirector instance for method chaining */ - public ManagerDirector registerAndUpdateInventoryAsset(String fileName) { - return registerAndUpdateInventoryAsset(fileName, false); + public ManagerDirector registerAndUpdateBlobInventory(String fileName) { + return registerAndUpdateBlobInventory(fileName, false); + } + + /** + * Will detach an embedded Inventory file/asset from the plugin jar to + * the corresponding directory in the plugin data folder. + * + * @param fileName The name of the file to detach + * @param debug Whether to print debug messages + * @return The ManagerDirector instance for method chaining + */ + public ManagerDirector registerAndUpdateMetaBlobInventory(String fileName, boolean debug) { + File path = getRealFileManager().metaInventoriesDirectory(); + File file = new File(path + "/" + fileName + ".yml"); + if (!blobFileManager.updateYAML(file)) + return this; + InventoryManager.continueLoadingMetaInventories(plugin, file); + if (debug) + getPlugin().getAnjoLogger().debug(" inventory asset " + fileName + ".yml successfully registered"); + return this; + } + + /** + * Will detach an embedded Inventory file/asset from the plugin jar to + * the corresponding directory in the plugin data folder. + * Will not print debug messages. + * + * @param fileName The name of the file to detach + * @return The ManagerDirector instance for method chaining + */ + public ManagerDirector registerAndUpdateMetaBlobInventory(String fileName) { + return registerAndUpdateMetaBlobInventory(fileName, false); + } + + private ManagerDirector registerAsset(String fileName, boolean debug, + File path, String type) { + BlobPlugin plugin = getPlugin(); + fileName = fileName + ".yml"; + File file = new File(path + "/" + fileName); + if (file.exists()) { + if (debug) + getPlugin().getAnjoLogger().debug(" " + type + " asset " + + fileName + ".yml was not registered, already exists"); + return this; + } + try { + FileUtils.copyToFile(plugin.getResource(fileName), file); + if (debug) + getPlugin().getAnjoLogger().debug(" " + type + " asset " + + fileName + ".yml successfully registered"); + return this; + } catch (IOException e) { + e.printStackTrace(); + return this; + } + } + + public ManagerDirector registerMetaBlobInventory(String fileName, boolean debug) { + return registerAsset(fileName, debug, getRealFileManager().metaInventoriesDirectory(), "inventory"); + } + + public ManagerDirector registerMetaBlobInventory(String fileName) { + return registerMetaBlobInventory(fileName, false); + } + + public ManagerDirector registerBlobInventory(String fileName, boolean debug) { + return registerAsset(fileName, debug, getRealFileManager().inventoriesDirectory(), "inventory"); + } + + public ManagerDirector registerBlobInventory(String fileName) { + return registerBlobInventory(fileName, false); + } + + public ManagerDirector registerBlobMessage(String fileName, boolean debug) { + return registerAsset(fileName, debug, getRealFileManager().messagesDirectory(), "message"); + } + + public ManagerDirector registerBlobMessage(String fileName) { + return registerBlobMessage(fileName, false); + } + + public ManagerDirector registerBlobSound(String fileName, boolean debug) { + return registerAsset(fileName, debug, getRealFileManager().soundsDirectory(), "sound"); + } + + public ManagerDirector registerBlobSound(String fileName) { + return registerBlobSound(fileName, false); } protected Set> getManagerEntry() { diff --git a/src/main/java/us/mytheria/bloblib/managers/MessageManager.java b/src/main/java/us/mytheria/bloblib/managers/MessageManager.java index fd161e95..12e230cb 100644 --- a/src/main/java/us/mytheria/bloblib/managers/MessageManager.java +++ b/src/main/java/us/mytheria/bloblib/managers/MessageManager.java @@ -10,15 +10,14 @@ import javax.annotation.Nullable; import java.io.File; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Set; +import java.util.*; public class MessageManager { private final BlobLib main; private HashMap messages; private HashMap> pluginMessages; private HashMap duplicates; + private HashMap> locales; public MessageManager() { this.main = BlobLib.getInstance(); @@ -30,6 +29,7 @@ public void reload() { public void load() { messages = new HashMap<>(); + locales = new HashMap<>(); pluginMessages = new HashMap<>(); duplicates = new HashMap<>(); loadFiles(main.getFileManager().messagesDirectory()); @@ -37,7 +37,7 @@ public void load() { .log("Duplicate BlobMessage: '" + key + "' (found " + value + " instances)")); } - public void load(BlobPlugin plugin, ManagerDirector director) { + public void load(BlobPlugin plugin, IManagerDirector director) { String pluginName = plugin.getName(); if (pluginMessages.containsKey(pluginName)) throw new IllegalArgumentException("Plugin '" + pluginName + "' has already been loaded"); @@ -51,9 +51,15 @@ public void load(BlobPlugin plugin, ManagerDirector director) { public void unload(BlobPlugin plugin) { String pluginName = plugin.getName(); - if (!pluginMessages.containsKey(pluginName)) + Set messages = this.pluginMessages.get(pluginName); + if (messages == null) return; - pluginMessages.get(pluginName).forEach(messages::remove); + Iterator iterator = messages.iterator(); + while (iterator.hasNext()) { + String inventoryName = iterator.next(); + this.messages.remove(inventoryName); + iterator.remove(); + } pluginMessages.remove(pluginName); } @@ -61,7 +67,7 @@ public static void unloadBlobPlugin(BlobPlugin plugin) { BlobLib.getInstance().getMessageManager().unload(plugin); } - public static void loadBlobPlugin(BlobPlugin plugin, ManagerDirector director) { + public static void loadBlobPlugin(BlobPlugin plugin, IManagerDirector director) { MessageManager manager = BlobLib.getInstance().getMessageManager(); manager.load(plugin, director); } @@ -108,7 +114,9 @@ private void loadYamlConfiguration(File file) { addDuplicate(reference); return; } - messages.put(reference, BlobMessageReader.read(section)); + SerialBlobMessage message = BlobMessageReader.read(section); + messages.put(reference, message); + addOrCreateLocale(message, reference); }); } @@ -129,6 +137,11 @@ private void loadYamlConfiguration(File file, BlobPlugin plugin) { }); } + private void addOrCreateLocale(SerialBlobMessage message, String reference) { + String locale = message.getLocale(); + locales.computeIfAbsent(locale, k -> new HashMap<>()).put(reference, message); + } + /** * Loads a YamlConfiguration from a file and registers it to the plugin. * Will return false if the plugin has not been loaded. @@ -157,7 +170,7 @@ private void addDuplicate(String key) { } public void noPermission(Player player) { - messages.get("System.No-Permission").sendAndPlay(player); + messages.get("System.No-Permission").handle(player); } @Nullable @@ -165,11 +178,19 @@ public ReferenceBlobMessage getMessage(String key) { return new ReferenceBlobMessage(messages.get(key), key); } + @Nullable + public ReferenceBlobMessage getMessage(String key, String locale) { + Map localeMap = locales.get(locale); + if (localeMap == null) + return null; + return new ReferenceBlobMessage(localeMap.get(key), key); + } + public void playAndSend(Player player, String key) { SerialBlobMessage message = messages.get(key); if (message == null) throw new NullPointerException("Message '" + key + "' does not exist!"); - message.sendAndPlay(player); + message.handle(player); } public void send(Player player, String key) { diff --git a/src/main/java/us/mytheria/bloblib/managers/MetaInventoryShard.java b/src/main/java/us/mytheria/bloblib/managers/MetaInventoryShard.java new file mode 100644 index 00000000..1b3df9d1 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/managers/MetaInventoryShard.java @@ -0,0 +1,75 @@ +package us.mytheria.bloblib.managers; + +import org.jetbrains.annotations.Nullable; +import us.mytheria.bloblib.entities.inventory.InventoryBuilderCarrier; +import us.mytheria.bloblib.entities.inventory.MetaInventoryButton; +import us.mytheria.bloblib.entities.inventory.ReferenceMetaBlobInventory; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class MetaInventoryShard { + private Map> inventories; + + public MetaInventoryShard() { + inventories = new HashMap<>(); + } + + protected void reload() { + inventories = new HashMap<>(); + } + + protected void addInventory(InventoryBuilderCarrier carrier, String key) { + inventories.put(key, carrier); + } + + @Nullable + public InventoryBuilderCarrier getMetaInventoryBuilderCarrier(String key) { + return inventories.get(key); + } + + /** + * Gets an inventory by its key. + * If not found, will return null. + * + * @param key Key that points to the inventory + * @return The inventory or null if not found + */ + @Nullable + public ReferenceMetaBlobInventory getInventory(String key) { + InventoryBuilderCarrier carrier = getMetaInventoryBuilderCarrier(key); + if (carrier == null) + return null; + return ReferenceMetaBlobInventory.of(carrier); + } + + /** + * Gets an inventory by its key and makes a copy of it. + * If not found, will return null. + * + * @param key Key that points to the inventory + * @return The inventory or null if not found + */ + @Nullable + public ReferenceMetaBlobInventory copyInventory(String key) { + ReferenceMetaBlobInventory inventory = getInventory(key); + if (inventory == null) + return null; + return inventory.copy(); + } + + /** + * @return All inventories held by this shard. + */ + public List allInventories() { + return inventories.values().stream().map(ReferenceMetaBlobInventory::of).toList(); + } + + /** + * @return The size of the shard. Each inventory counts as 1. + */ + public int size() { + return inventories.size(); + } +} diff --git a/src/main/java/us/mytheria/bloblib/managers/PluginManager.java b/src/main/java/us/mytheria/bloblib/managers/PluginManager.java index 39da6bc1..75131b0c 100644 --- a/src/main/java/us/mytheria/bloblib/managers/PluginManager.java +++ b/src/main/java/us/mytheria/bloblib/managers/PluginManager.java @@ -1,9 +1,12 @@ package us.mytheria.bloblib.managers; +import org.jetbrains.annotations.Nullable; import us.mytheria.bloblib.BlobLib; import us.mytheria.bloblib.utilities.Debug; +import java.util.Collection; import java.util.HashMap; +import java.util.Objects; /** * @author anjoismysign @@ -48,13 +51,34 @@ public void reload() { plugins.values().forEach(BlobPlugin::blobLibReload); } + /** + * Will return a Collection of all the BlobPlugins. + * + * @return A Collection of all the BlobPlugins. + */ + public Collection values() { + return plugins.values(); + } + + /** + * Will return the BlobPlugin with the given name. + * + * @param name The name of the BlobPlugin. + * @return The BlobPlugin with the given name. + * null if there is no BlobPlugin with the given name. + */ + @Nullable + public BlobPlugin get(String name) { + return plugins.get(name); + } + /** * This method should be called whenever a BlobPlugin is enabled. * It inserts it inside a HashMap which will be used to call * the 'blobLibReload()' method. Will also load all assets * that can be used by BlobLib. */ - public static void registerPlugin(BlobPlugin plugin, ManagerDirector director) { + public static void registerPlugin(BlobPlugin plugin, IManagerDirector director) { PluginManager manager = BlobLib.getInstance().getPluginManager(); manager.put(plugin); loadAssets(plugin, director); @@ -75,13 +99,17 @@ public static void unregisterPlugin(BlobPlugin plugin) { public static void unloadAssets(BlobPlugin plugin) { InventoryManager.unloadBlobPlugin(plugin); + ActionManager.unloadBlobPlugin(plugin); MessageManager.unloadBlobPlugin(plugin); SoundManager.unloadBlobPlugin(plugin); } - public static void loadAssets(BlobPlugin plugin, ManagerDirector director) { + public static void loadAssets(BlobPlugin plugin, IManagerDirector director) { + director = Objects.requireNonNull(director, + plugin.getName() + "'s ManagerDirector is null!"); SoundManager.loadBlobPlugin(plugin, director); MessageManager.loadBlobPlugin(plugin, director); + ActionManager.loadBlobPlugin(plugin, director); InventoryManager.loadBlobPlugin(plugin, director); } diff --git a/src/main/java/us/mytheria/bloblib/managers/SelPosListenerManager.java b/src/main/java/us/mytheria/bloblib/managers/SelPosListenerManager.java index 02b12bf6..5cdf1c24 100644 --- a/src/main/java/us/mytheria/bloblib/managers/SelPosListenerManager.java +++ b/src/main/java/us/mytheria/bloblib/managers/SelPosListenerManager.java @@ -31,9 +31,9 @@ public void onLeftClick(PlayerInteractEvent e) { if (e.getHand() != EquipmentSlot.HAND) return; Player player = e.getPlayer(); - if (!positionListeners.containsKey(player.getName())) - return; SelPosListener listener = positionListeners.get(player.getName()); + if (listener == null) + return; listener.setInput(e.getClickedBlock()); e.setCancelled(true); } @@ -45,9 +45,9 @@ public void onRightClick(PlayerInteractEvent e) { if (e.getHand() != EquipmentSlot.HAND) return; Player player = e.getPlayer(); - if (!positionListeners.containsKey(player.getName())) - return; SelPosListener listener = positionListeners.get(player.getName()); + if (listener == null) + return; listener.setInput(e.getClickedBlock()); e.setCancelled(true); } diff --git a/src/main/java/us/mytheria/bloblib/managers/SelectorListenerManager.java b/src/main/java/us/mytheria/bloblib/managers/SelectorListenerManager.java index 726e462c..1cc77a29 100644 --- a/src/main/java/us/mytheria/bloblib/managers/SelectorListenerManager.java +++ b/src/main/java/us/mytheria/bloblib/managers/SelectorListenerManager.java @@ -2,6 +2,7 @@ import org.bukkit.entity.Player; import us.mytheria.bloblib.BlobLib; +import us.mytheria.bloblib.entities.listeners.EditorListener; import us.mytheria.bloblib.entities.listeners.SelectorListener; import javax.annotation.Nullable; @@ -10,30 +11,52 @@ public class SelectorListenerManager { private final BlobLib main; private final HashMap> selectorListener; + private final HashMap> editorListener; public SelectorListenerManager() { this.main = BlobLib.getInstance(); this.selectorListener = new HashMap<>(); + this.editorListener = new HashMap<>(); } - public void addSelectorListener(Player player, SelectorListener listener) { + public boolean addSelectorListener(Player player, SelectorListener listener) { String name = player.getName(); if (selectorListener.containsKey(name)) { main.getMessageManager().playAndSend(player, "System.Already-Selector-Listening"); - return; + return false; } listener.runTasks(); selectorListener.put(player.getName(), listener); + return true; + } + + public boolean addEditorListener(Player player, EditorListener listener) { + String name = player.getName(); + if (editorListener.containsKey(name)) { + main.getMessageManager().playAndSend(player, "System.Already-Editor-Listening"); + return false; + } + listener.runTasks(); + editorListener.put(player.getName(), listener); + return true; } public void removeSelectorListener(Player player) { removeSelectorListener(player.getName()); } + public void removeEditorListener(Player player) { + removeEditorListener(player.getName()); + } + public void removeSelectorListener(String string) { selectorListener.remove(string); } + public void removeEditorListener(String string) { + editorListener.remove(string); + } + public void cancelSelectorListener(String string) { SelectorListener selectorListener = this.selectorListener.get(string); if (selectorListener != null) { @@ -42,22 +65,44 @@ public void cancelSelectorListener(String string) { } } + public void cancelEditorListener(String string) { + EditorListener editorListener = this.editorListener.get(string); + if (editorListener != null) { + editorListener.cancel(); + removeEditorListener(string); + } + } + public void cancelSelectorListener(Player player) { cancelSelectorListener(player.getName()); } + public void cancelEditorListener(Player player) { + cancelEditorListener(player.getName()); + } + @Nullable public Object getInput(Player player) { return selectorListener.get(player.getName()).getInput(); } @Nullable - public SelectorListener get(Player player) { - return get(player.getName()); + public SelectorListener getSelectorListener(Player player) { + return getSelectorListener(player.getName()); } @Nullable - public SelectorListener get(String string) { + public SelectorListener getSelectorListener(String string) { return selectorListener.get(string); } + + @Nullable + public EditorListener getEditorListener(Player player) { + return getEditorListener(player.getName()); + } + + @Nullable + public EditorListener getEditorListener(String string) { + return editorListener.get(string); + } } diff --git a/src/main/java/us/mytheria/bloblib/managers/SoundManager.java b/src/main/java/us/mytheria/bloblib/managers/SoundManager.java index 7060bfc2..ba7e0a4c 100644 --- a/src/main/java/us/mytheria/bloblib/managers/SoundManager.java +++ b/src/main/java/us/mytheria/bloblib/managers/SoundManager.java @@ -11,6 +11,7 @@ import java.io.File; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.Set; public class SoundManager { @@ -36,7 +37,7 @@ public void load() { .severe("Duplicate BlobSound: '" + key + "' (found " + value + " instances)")); } - public void load(BlobPlugin plugin, ManagerDirector director) { + public void load(BlobPlugin plugin, IManagerDirector director) { String pluginName = plugin.getName(); if (pluginSounds.containsKey(pluginName)) throw new IllegalArgumentException("Plugin '" + pluginName + "' has already been loaded"); @@ -48,16 +49,22 @@ public void load(BlobPlugin plugin, ManagerDirector director) { .severe("Duplicate BlobSound: '" + key + "' (found " + value + " instances)")); } - public static void loadBlobPlugin(BlobPlugin plugin, ManagerDirector director) { + public static void loadBlobPlugin(BlobPlugin plugin, IManagerDirector director) { SoundManager manager = BlobLib.getInstance().getSoundManager(); manager.load(plugin, director); } public void unload(BlobPlugin plugin) { String pluginName = plugin.getName(); - if (!pluginSounds.containsKey(pluginName)) + Set sounds = this.pluginSounds.get(pluginName); + if (sounds == null) return; - pluginSounds.get(pluginName).forEach(sounds::remove); + Iterator iterator = sounds.iterator(); + while (iterator.hasNext()) { + String inventoryName = iterator.next(); + this.sounds.remove(inventoryName); + iterator.remove(); + } pluginSounds.remove(pluginName); } diff --git a/src/main/java/us/mytheria/bloblib/managers/VariableSelectorManager.java b/src/main/java/us/mytheria/bloblib/managers/VariableSelectorManager.java index dbb29af7..89633d3f 100644 --- a/src/main/java/us/mytheria/bloblib/managers/VariableSelectorManager.java +++ b/src/main/java/us/mytheria/bloblib/managers/VariableSelectorManager.java @@ -1,39 +1,83 @@ package us.mytheria.bloblib.managers; import org.bukkit.Bukkit; -import org.bukkit.Sound; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.player.PlayerQuitEvent; import us.mytheria.bloblib.BlobLib; +import us.mytheria.bloblib.api.BlobLibSoundAPI; +import us.mytheria.bloblib.entities.BlobEditor; import us.mytheria.bloblib.entities.inventory.VariableSelector; +import us.mytheria.bloblib.entities.listeners.EditorListener; import us.mytheria.bloblib.entities.listeners.SelectorListener; +import us.mytheria.bloblib.entities.message.BlobSound; import java.util.HashMap; public class VariableSelectorManager implements Listener { private final BlobLib main; private final HashMap> variableSelectors; + private final HashMap> blobEditors; public VariableSelectorManager() { this.main = BlobLib.getInstance(); Bukkit.getPluginManager().registerEvents(this, BlobLib.getInstance()); this.variableSelectors = new HashMap<>(); + this.blobEditors = new HashMap<>(); } + @EventHandler + public void onEditorClick(InventoryClickEvent event) { + Player player = (Player) event.getWhoClicked(); + BlobEditor blobEditor = blobEditors.get(player.getName()); + if (blobEditor == null) { + return; + } + EditorListener listener = main.getSelectorManager().getEditorListener(player); + if (listener == null) { + return; + } + event.setCancelled(true); + int slot = event.getRawSlot(); + BlobSound clickSound = BlobLibSoundAPI.getInstance().getSound("Builder.Button-Click"); + if (slot > blobEditor.valuesSize() - 1) { + if (blobEditor.isNextPageButton(slot)) { + blobEditor.nextPage(); + return; + } + if (blobEditor.isPreviousPageButton(slot)) { + blobEditor.previousPage(); + return; + } + if (blobEditor.isAddElementButton(slot)) { + clickSound.handle(player); + blobEditor.addElement(player); + return; + } + if (blobEditor.isRemoveElementButton(slot)) { + clickSound.handle(player); + blobEditor.removeElement(player); + return; + } + return; + } + listener.setInputFromSlot(blobEditor, event.getRawSlot()); + clickSound.handle(player); + } @EventHandler - public void onClick(InventoryClickEvent e) { + public void onSelectorClick(InventoryClickEvent e) { Player player = (Player) e.getWhoClicked(); - if (!variableSelectors.containsKey(player.getName())) + VariableSelector variableSelector = variableSelectors.get(player.getName()); + if (variableSelector == null) return; - SelectorListener listener = main.getSelectorManager().get(player); + SelectorListener listener = main.getSelectorManager().getSelectorListener(player); if (listener == null) return; e.setCancelled(true); - VariableSelector variableSelector = variableSelectors.get(player.getName()); int slot = e.getRawSlot(); if (slot > variableSelector.valuesSize() - 1) { if (variableSelector.isNextPageButton(slot)) { @@ -46,21 +90,61 @@ public void onClick(InventoryClickEvent e) { return; } listener.setInputFromSlot(variableSelector, slot); - player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 1, 1); + BlobLibSoundAPI.getInstance().getSound("Builder.Button-Click").handle(player); } @EventHandler - public void onClose(InventoryCloseEvent e) { + public void onSelectorClose(InventoryCloseEvent e) { Player player = (Player) e.getPlayer(); if (!variableSelectors.containsKey(player.getName())) return; - SelectorListener listener = main.getSelectorManager().get(player); + SelectorListener listener = main.getSelectorManager().getSelectorListener(player); if (listener == null) return; listener.setInput(null); } + @EventHandler + public void onEditorClose(InventoryCloseEvent e) { + Player player = (Player) e.getPlayer(); + if (!blobEditors.containsKey(player.getName())) + return; + EditorListener listener = main.getSelectorManager().getEditorListener(player); + if (listener == null) + return; + listener.setInput(null); + } + + @EventHandler + public void onQuit(PlayerQuitEvent event) { + removeMapping(event.getPlayer()); + } + + /** + * Adds a new VariableSelector to the mapping + * + * @param variableSelector The VariableSelector + */ public void addVariableSelector(VariableSelector variableSelector) { variableSelectors.put(variableSelector.getPlayer().getName(), variableSelector); } + + /** + * Adds a new BlobEditor to the mapping + * + * @param blobEditor The BlobEditor + */ + public void addEditorSelector(BlobEditor blobEditor) { + blobEditors.put(blobEditor.getPlayer().getName(), blobEditor); + } + + /** + * Removes the mapping of the player + * + * @param player The player + */ + public void removeMapping(Player player) { + variableSelectors.remove(player.getName()); + blobEditors.remove(player.getName()); + } } diff --git a/src/main/java/us/mytheria/bloblib/reflection/BlobReflectionLib.java b/src/main/java/us/mytheria/bloblib/reflection/BlobReflectionLib.java new file mode 100644 index 00000000..e682f22f --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/reflection/BlobReflectionLib.java @@ -0,0 +1,47 @@ +package us.mytheria.bloblib.reflection; + +import org.bukkit.Bukkit; +import us.mytheria.bloblib.BlobLib; +import us.mytheria.bloblib.reflection.nonlivingentity.NLEWrapper1_20_R1; +import us.mytheria.bloblib.reflection.nonlivingentity.NonLivingEntityWrapper; + +public class BlobReflectionLib { + private static BlobReflectionLib instance; + private final NonLivingEntityWrapper nleWrapper; + private final BlobLib plugin; + + private BlobReflectionLib(BlobLib plugin) { + this.plugin = plugin; + String nmsVersion = getNMSVersion(); + if (!nmsVersion.equals("1_20_R1")) + BlobLib.getAnjoLogger().singleError("Unsupported NMS version: " + nmsVersion); + this.nleWrapper = new NLEWrapper1_20_R1(); + } + + public static BlobReflectionLib getInstance(BlobLib plugin) { + if (instance == null) { + if (plugin == null) + throw new NullPointerException("injected dependency is null"); + BlobReflectionLib.instance = new BlobReflectionLib(plugin); + } + return instance; + } + + public static BlobReflectionLib getInstance() { + return getInstance(null); + } + + public NonLivingEntityWrapper getNonLivingEntityWrapper() { + return nleWrapper; + } + + public String getNMSVersion() { + String v = Bukkit.getServer().getClass().getPackage().getName(); + v = v.substring(v.lastIndexOf('.') + 1); + if (v.startsWith("v_")) + v = v.substring(2); + if (v.startsWith("v")) + v = v.substring(1); + return v; + } +} diff --git a/src/main/java/us/mytheria/bloblib/reflection/nonlivingentity/NLEWrapper1_20_R1.java b/src/main/java/us/mytheria/bloblib/reflection/nonlivingentity/NLEWrapper1_20_R1.java new file mode 100644 index 00000000..dd79dfa8 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/reflection/nonlivingentity/NLEWrapper1_20_R1.java @@ -0,0 +1,35 @@ +package us.mytheria.bloblib.reflection.nonlivingentity; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Entity; + +import java.lang.reflect.Method; +import java.util.function.Supplier; + +public class NLEWrapper1_20_R1 implements NonLivingEntityWrapper { + private final Method[] methods = ((Supplier) () -> { + try { + Method getHandle = Class.forName(Bukkit.getServer().getClass().getPackage().getName() + ".entity.CraftEntity").getDeclaredMethod("getHandle"); + return new Method[]{ + getHandle, getHandle.getReturnType().getDeclaredMethod("b", double.class, double.class, double.class, float.class, float.class) + }; + } catch (Exception ex) { + ex.printStackTrace(); + return null; + } + }).get(); + + public void vehicleTeleport(Entity vehicle, Location location) { + try { + methods[1].invoke(methods[0].invoke(vehicle), + location.getX(), + location.getY(), + location.getZ(), + location.getYaw(), + location.getPitch()); + } catch (Exception ex) { + ex.printStackTrace(); + } + } +} diff --git a/src/main/java/us/mytheria/bloblib/reflection/nonlivingentity/NonLivingEntityWrapper.java b/src/main/java/us/mytheria/bloblib/reflection/nonlivingentity/NonLivingEntityWrapper.java new file mode 100644 index 00000000..cf1e7d2c --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/reflection/nonlivingentity/NonLivingEntityWrapper.java @@ -0,0 +1,17 @@ +package us.mytheria.bloblib.reflection.nonlivingentity; + +import org.bukkit.Location; +import org.bukkit.entity.Entity; + +public interface NonLivingEntityWrapper { + + /** + * Will teleport the entity to the location including passengers + * without ejecting them. This allows a smooth client-side interpolation + * which simulates that both vehicle and passenger are moving. + * + * @param vehicle the entity in which the passengers are in + * @param location the location to teleport the entity to + */ + void vehicleTeleport(Entity vehicle, Location location); +} diff --git a/src/main/java/us/mytheria/bloblib/storage/BlobCrudManager.java b/src/main/java/us/mytheria/bloblib/storage/BlobCrudManager.java new file mode 100644 index 00000000..13e7ca44 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/storage/BlobCrudManager.java @@ -0,0 +1,228 @@ +package us.mytheria.bloblib.storage; + +import me.anjoismysign.anjo.crud.CrudManager; +import me.anjoismysign.anjo.logger.Logger; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import us.mytheria.bloblib.entities.BlobCrudable; + +import java.util.UUID; + +public class BlobCrudManager implements CrudManager { + private final CrudManager crudManager; + private final StorageType storageType; + private final IdentifierType identifierType; + + public BlobCrudManager(CrudManager crudManager, + StorageType storageType, + IdentifierType identifierType) { + this.crudManager = crudManager; + this.storageType = storageType; + this.identifierType = identifierType; + } + + /** + * Returns the storage type of the BlobCrudManager. + * + * @return The storage type of the BlobCrudManager. + */ + public StorageType getStorageType() { + return storageType; + } + + /** + * Returns the identifier type of the BlobCrudManager. + * + * @return The identifier type of the BlobCrudManager. + */ + public IdentifierType getIdentifierType() { + return identifierType; + } + + /** + * Returns whether the BlobCrudable with the specified identification exists. + * + * @param s The identification of the BlobCrudable. + * @return Whether the BlobCrudable with the specified identification exists. + */ + @Override + public boolean exists(String s) { + return crudManager.exists(s); + } + + /** + * Returns whether the BlobCrudable with the identification of the player exists. + * + * @param player The player to check the BlobCrudable for. + * @return Whether the BlobCrudable with the identification of the player exists. + */ + public boolean exists(Player player) { + if (identifierType == IdentifierType.UUID) + return exists(player.getUniqueId().toString()); + else if (identifierType == IdentifierType.PLAYERNAME) + return exists(player.getName()); + else + throw new IllegalStateException("IdentifierType is not set to UUID or PLAYERNAME: " + + identifierType); + } + + /** + * Creates new BlobCrudable with the given identification and returns it. + * + * @param s The identification of the BlobCrudable. + * @return The created BlobCrudable. + */ + @Override + public T create(String s) { + return crudManager.create(s); + } + + /** + * Creates new BlobCrudable with the given identification and returns it. + * + * @param identification The identification of the BlobCrudable. + * @return The created BlobCrudable. + */ + @NotNull + public T createOrFail(String identification) { + if (crudManager.exists(identification)) + throw new IllegalStateException("Identifier already exists: " + identification); + return crudManager.create(identification); + } + + /** + * Creates new BlobCrudable with a random identification and returns it. + * Identification is a random UUID. If BlobCrudable was lucky enough + * to generate an existing UUID, it will throw an IllegalStateException. + * + * @return The created BlobCrudable. + */ + @NotNull + public T createRandomOrFail() { + String identification = UUID.randomUUID().toString(); + return createOrFail(identification); + } + + /** + * Will create a new BlobCrudable with the identification of the player. + * It's based on the IdentifierType from the BlobCrudManager. + * + * @param player The player to create the BlobCrudable for. + * @return The created BlobCrudable. + */ + public T create(Player player) { + if (identifierType == IdentifierType.UUID) + return create(player.getUniqueId().toString()); + else if (identifierType == IdentifierType.PLAYERNAME) + return create(player.getName()); + else + throw new IllegalStateException("IdentifierType is not set to UUID or PLAYERNAME: " + + identifierType); + } + + /** + * Will read the BlobCrudable with the specified identification. + * + * @param s The identification of the BlobCrudable. + * @return The BlobCrudable. + */ + @NotNull + @Override + public T read(String s) { + return crudManager.read(s); + } + + /** + * Will read the BlobCrudable with the identification of the player. + * It will assume that identification type matches the IdentifierType + * + * @param player The player to read the BlobCrudable for. + * @return The BlobCrudable. + */ + @NotNull + public T read(Player player) { + if (identifierType == IdentifierType.UUID) + return read(player.getUniqueId().toString()); + else if (identifierType == IdentifierType.PLAYERNAME) + return read(player.getName()); + else + throw new IllegalStateException("IdentifierType is not set to UUID or PLAYERNAME: " + + identifierType); + } + + /** + * Will read the BlobCrudable with the specified identification. + * + * @param s The identification of the BlobCrudable. + * @return The BlobCrudable or null if it doesn't exist. + */ + @Nullable + @Override + public T readOrNull(String s) { + return crudManager.readOrNull(s); + } + + /** + * Will read the BlobCrudable with the identification of the player. + * + * @param player The player to read the BlobCrudable for. + * @return The BlobCrudable or null if it doesn't exist. + */ + @Nullable + public T readOrNull(Player player) { + if (identifierType == IdentifierType.UUID) + return readOrNull(player.getUniqueId().toString()); + else if (identifierType == IdentifierType.PLAYERNAME) + return readOrNull(player.getName()); + else + throw new IllegalStateException("IdentifierType is not set to UUID or PLAYERNAME: " + + identifierType); + } + + /** + * Will update the BlobCrudable. + * + * @param t The BlobCrudable to update. + */ + @Override + public void update(T t) { + crudManager.update(t); + } + + /** + * Will delete the BlobCrudable with the specified identification. + * + * @param s The identification of the BlobCrudable. + */ + @Override + public void delete(String s) { + crudManager.delete(s); + } + + /** + * Will delete the BlobCrudable with the identification of the player. + * + * @param player The player to delete the BlobCrudable for. + */ + public void delete(Player player) { + if (identifierType == IdentifierType.UUID) + delete(player.getUniqueId().toString()); + else if (identifierType == IdentifierType.PLAYERNAME) + delete(player.getName()); + else + throw new IllegalStateException("IdentifierType is not set to UUID or PLAYERNAME: " + + identifierType); + } + + /** + * Will return the logger of the CrudManager. + * + * @return The logger of the CrudManager. + */ + @Nullable + @Override + public Logger getLogger() { + return crudManager.getLogger(); + } +} diff --git a/src/main/java/us/mytheria/bloblib/storage/IdentifierType.java b/src/main/java/us/mytheria/bloblib/storage/IdentifierType.java new file mode 100644 index 00000000..e6a9f704 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/storage/IdentifierType.java @@ -0,0 +1,6 @@ +package us.mytheria.bloblib.storage; + +public enum IdentifierType { + UUID, + PLAYERNAME +} diff --git a/src/main/java/us/mytheria/bloblib/storage/MongoCrudManager.java b/src/main/java/us/mytheria/bloblib/storage/MongoCrudManager.java new file mode 100644 index 00000000..3688002c --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/storage/MongoCrudManager.java @@ -0,0 +1,152 @@ +package us.mytheria.bloblib.storage; + +import me.anjoismysign.anjo.crud.CrudManager; +import me.anjoismysign.anjo.entities.Result; +import me.anjoismysign.anjo.logger.Logger; +import org.bson.Document; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import us.mytheria.bloblib.entities.BlobCrudable; + +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Supplier; + +public class MongoCrudManager implements CrudManager { + private final MongoDB mongoDB; + private final String collection; + private final Function createFunction; + private final Logger logger; + + public MongoCrudManager(MongoDB mongoDB, String collection, + Function createFunction, + Logger logger) { + this.mongoDB = mongoDB; + this.collection = collection; + this.createFunction = createFunction; + this.logger = logger; + load(); + } + + public void load() { + boolean isNewCollection = !mongoDB.collectionExists(collection); + if (isNewCollection) + log("Create collection '" + collection + "' was executed successfully."); + } + + /** + * @param id The primary key id + * @return Whether the record exists + */ + @Override + public boolean exists(String id) { + Result result = mongoDB.getDocument(collection, new Document("_id", id)); + return result.isValid(); + } + + /** + * Creates a new instance of the Crudable and registers it in the database + * using the given identification. Will only update the identification. + * + * @param identification The identification to use. + * @return The new instance of the Crudable. + */ + @Override + public T create(String identification) { + T crudable = createFunction.apply(identification); + Document document = crudable.getDocument(); + document.put("_id", identification); + mongoDB.insertOne(collection, document); + log("Created new with id " + identification + " in collection " + collection); + return crudable; + } + + /** + * Will attempt to read the Crudable with the given id from the database. + * If not found, will create a new instance of the Crudable and register it + * using the given id. + * + * @param id The id of the Crudable to get + * @return The Crudable with the given id + */ + @NotNull + @Override + public T read(String id) { + return readOrGenerate(id, () -> create(id)); + } + + /** + * Will attempt to read the Crudable with the given id from the database. + * If not found/exists, will return null. + * + * @param id The id of the Crudable to get + * @return The Crudable with the given id + */ + @Nullable + @Override + public T readOrNull(String id) { + return readOrGenerate(id, () -> null); + } + + private T readOrGenerate(String id, Supplier replacement) { + Result result = mongoDB.getDocument(collection, new Document("_id", id)); + if (result.isValid()) { + Document document = result.value(); + T crudable = createFunction.apply(id); + crudable.getDocument().putAll(document); + log("Read " + id + " from collection " + collection); + return crudable; + } + log("Record '" + id + "' not found from collection: " + collection); + return replacement.get(); + } + + /** + * @param crudable The Crudable to be updated (defaults version to 0) + */ + @Override + public void update(T crudable) { + Document document = crudable.getDocument(); + String id = crudable.getIdentification(); + document.put("_id", id); + boolean succesful = mongoDB.replaceOne(collection, new Document("_id", id), document); + if (succesful) + log("Updated '" + crudable.getIdentification() + "' in collection " + collection); + else + log("Failed to update '" + crudable.getIdentification() + "' in collection " + collection); + } + + /** + * @param id The id of the Crudable to delete + */ + @Override + public void delete(String id) { + boolean success = mongoDB.deleteOne(collection, new Document("_id", id)); + if (success) + log("Deleted '" + id + "' from collection " + collection); + else + log("Failed to delete '" + id + "' from collection " + collection); + } + + /** + * @param biConsumer First parameter is Crudable. + * Second parameter is useless in MongoCrudManager. + */ + public void forEachRecord(BiConsumer biConsumer) { + mongoDB.selectAllFromCollection(collection, document -> { + T crudable = createFunction.apply(document.getString("_id")); + crudable.getDocument().putAll(document); + biConsumer.accept(crudable, 0); + }); + } + + @Override + public Logger getLogger() { + return logger; + } + + private void log(String message) { + if (logger != null) + logger.log(message); + } +} diff --git a/src/main/java/us/mytheria/bloblib/storage/MongoDB.java b/src/main/java/us/mytheria/bloblib/storage/MongoDB.java index 444a225a..85613e3a 100644 --- a/src/main/java/us/mytheria/bloblib/storage/MongoDB.java +++ b/src/main/java/us/mytheria/bloblib/storage/MongoDB.java @@ -3,11 +3,13 @@ import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; import com.mongodb.client.result.DeleteResult; import com.mongodb.client.result.UpdateResult; import me.anjoismysign.anjo.entities.Result; import org.bson.Document; import org.bukkit.configuration.ConfigurationSection; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.function.Consumer; @@ -22,15 +24,10 @@ */ public class MongoDB { private final String connection; + private final String database; + private final @Nullable String uri; - /** - * Will create a new MongoDB instance with the default localhost connection. - * - * @return A new MongoDB instance with the default localhost connection. - */ - public static MongoDB LOCALHOST() { - return new MongoDB("mongodb://localhost:27017"); - } + private final MongoClient mongoClient; /** * Will create a new MongoDB instance with the default localhost connection and the provided database. @@ -38,8 +35,8 @@ public static MongoDB LOCALHOST() { * @param database The database to use. * @return A new MongoDB instance with the default localhost connection and the provided database. */ - public static MongoDB LOCALHOST_DATABASE(String database) { - return new MongoDB("mongodb://localhost:27017/" + database); + public static MongoDB LOCALHOST(String database) { + return new MongoDB("mongodb://localhost:27017/", database, null); } /** @@ -49,36 +46,33 @@ public static MongoDB LOCALHOST_DATABASE(String database) { * @return A valid result if found MongoDB settings, otherwise an invalid result. */ public static Result fromConfigurationSection(ConfigurationSection configurationSection) { - if (configurationSection.contains("host") && configurationSection.isString("host") && - configurationSection.contains("port") && configurationSection.isInt("port") && - configurationSection.contains("database") && configurationSection.isString("database") && - configurationSection.contains("username") && configurationSection.isString("username") && - configurationSection.contains("password") && configurationSection.isString("password")) + if (configurationSection.isString("Hostname") && + configurationSection.isString("Database") && + configurationSection.isString("Username") && + configurationSection.isString("Password")) return Result.valid(loadFromConfigurationSection(configurationSection)); - for (String reference : configurationSection.getKeys(true)) { - if (!configurationSection.isConfigurationSection(reference)) - continue; - if (!reference.equalsIgnoreCase("mongodb")) - continue; - ConfigurationSection section = configurationSection.getConfigurationSection(reference); - if (section.contains("host") && section.isString("host") && - section.contains("port") && section.isInt("port") && - section.contains("database") && section.isString("database") && - section.contains("username") && section.isString("username") && - section.contains("password") && section.isString("password")) - return Result.valid(loadFromConfigurationSection(section)); - } + if (!configurationSection.isConfigurationSection("Database")) + return Result.invalidBecauseNull(); + ConfigurationSection section = configurationSection.getConfigurationSection("Database"); + if (section.isString("Hostname") && + section.isString("Database") && + section.isString("Username") && + section.isString("Password")) + return Result.valid(loadFromConfigurationSection(section)); return Result.invalidBecauseNull(); } + public static MongoDB fromURI(String uri, String database) { + return new MongoDB(uri, database, uri); + } + private static MongoDB loadFromConfigurationSection(ConfigurationSection configurationSection) { - String host = configurationSection.getString("host"); - int port = configurationSection.getInt("port"); - String database = configurationSection.getString("database"); - String username = configurationSection.getString("username"); - String password = configurationSection.getString("password"); - String connection = "mongodb://" + username + ":" + password + "@" + host + ":" + port + "/" + database; - return new MongoDB(connection); + String host = configurationSection.getString("Hostname"); + String database = configurationSection.getString("Database"); + String username = configurationSection.getString("Username"); + String password = configurationSection.getString("Password"); + String connection = "mongodb+srv://" + username + ":" + password + "@" + host + "/?retryWrites=true&w=majority"; + return new MongoDB(connection, database, null); } /** @@ -86,140 +80,140 @@ private static MongoDB loadFromConfigurationSection(ConfigurationSection configu * * @param connection The connection string to use. */ - public MongoDB(String connection) { + public MongoDB(String connection, String database, @Nullable String uri) { this.connection = connection; + this.database = database; + this.uri = uri; + this.mongoClient = connect(); } private MongoClient connect() { +// LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false); +// Configuration configuration = loggerContext.getConfiguration(); +// LoggerConfig loggerConfig = configuration.getLoggerConfig("org.mongodb.driver"); +// loggerConfig.setLevel(Level.WARN); + if (uri != null) + return MongoClients.create(uri); return MongoClients.create(connection); } + private MongoDatabase getDatabase() { + return mongoClient.getDatabase(this.database); + } + /** * Attempts to retrieve a Document from the provided collection and database. * - * @param database The database name to select from. * @param collection The collection name to select from. * @param searchQuery The document to filter by. * @return A valid result if found, otherwise an invalid result. */ - public Result getDocument(String database, String collection, Document searchQuery) { - try (MongoClient client = connect()) { - MongoCollection mongoCollection = client.getDatabase(database).getCollection(collection); - Document iterable = mongoCollection.find(searchQuery).first(); - return Result.ofNullable(iterable); - } + public Result getDocument(String collection, Document searchQuery) { + MongoCollection mongoCollection = getDatabase().getCollection(collection); + Document iterable = mongoCollection.find(searchQuery).first(); + return Result.ofNullable(iterable); } /** * Attempts to update the first document that matches the search query with the new values * provided in the newValues document. * - * @param database The database name to select from. * @param collection The collection name to select from. * @param searchQuery The document to filter by. - * @param newValues The document to update with. + * @param replacement The document to replace with * @return True if the document was updated, false otherwise. */ - public boolean updateOne(String database, String collection, Document searchQuery, - Document newValues) { - try (MongoClient client = connect()) { - MongoCollection mongoCollection = client.getDatabase(database).getCollection(collection); - Document updateQuery = new Document("$set", newValues); - UpdateResult result = mongoCollection.updateOne(searchQuery, updateQuery); - return result.getModifiedCount() > 0; - } + public boolean replaceOne(String collection, Document searchQuery, + Document replacement) { + MongoCollection mongoCollection = getDatabase().getCollection(collection); + UpdateResult result = mongoCollection.replaceOne(searchQuery, replacement); + return result.getModifiedCount() > 0; } /** * Attempts to update all documents that match the search query with the new values * provided in the newValues document. * - * @param database The database name to select from. * @param collection The collection name to select from. * @param searchQuery The document to filter by. * @param newValues The document to update with. * @return True if at least one document was updated, false otherwise. */ - public boolean updateMany(String database, String collection, Document searchQuery, + public boolean updateMany(String collection, Document searchQuery, Document newValues) { - try (MongoClient client = connect()) { - MongoCollection mongoCollection = client.getDatabase(database).getCollection(collection); - Document updateQuery = new Document("$set", newValues); - UpdateResult result = mongoCollection.updateMany(searchQuery, updateQuery); - return result.getModifiedCount() > 0; - } + MongoCollection mongoCollection = getDatabase().getCollection(collection); + Document updateQuery = new Document("$set", newValues); + UpdateResult result = mongoCollection.updateMany(searchQuery, updateQuery); + return result.getModifiedCount() > 0; } /** * Attempts to delete the first document that matches the search query. * - * @param database The database name to select from. * @param collection The collection name to select from. * @param searchQuery The document to filter by. * @return True if the document was deleted, false otherwise. */ - public boolean deleteOne(String database, String collection, Document searchQuery) { - try (MongoClient client = connect()) { - MongoCollection mongoCollection = client.getDatabase(database).getCollection(collection); - DeleteResult result = mongoCollection.deleteOne(searchQuery); - return result.getDeletedCount() > 0; - } - + public boolean deleteOne(String collection, Document searchQuery) { + MongoCollection mongoCollection = getDatabase().getCollection(collection); + DeleteResult result = mongoCollection.deleteOne(searchQuery); + return result.getDeletedCount() > 0; } /** * Attempts to delete all documents that match the search query. * - * @param database The database name to select from. * @param collection The collection name to select from. * @param searchQuery The document to filter by. * @return True if the document was deleted, false otherwise. */ - public boolean deleteMany(String database, String collection, Document searchQuery) { - try (MongoClient client = connect()) { - MongoCollection mongoCollection = client.getDatabase(database).getCollection(collection); - DeleteResult result = mongoCollection.deleteMany(searchQuery); - return result.getDeletedCount() > 0; - } + public boolean deleteMany(String collection, Document searchQuery) { + MongoCollection mongoCollection = getDatabase().getCollection(collection); + DeleteResult result = mongoCollection.deleteMany(searchQuery); + return result.getDeletedCount() > 0; } /** * Creates a new collection in the provided database. * - * @param database The database to create the collection in. * @param newCollection The name of the new collection. */ - public void createCollection(String database, String newCollection) { - try (MongoClient client = connect()) { - client.getDatabase(database).createCollection(newCollection); - } + public void createCollection(String newCollection) { + getDatabase().createCollection(newCollection); } /** * Checks if a collection exists in the provided database. * - * @param database The database to check in. * @param collection The collection to check for. * @return True if the collection exists, false otherwise. */ - public boolean collectionExists(String database, String collection) { - try (MongoClient client = connect()) { - return client.getDatabase(database).listCollectionNames().into(new ArrayList<>()).contains(collection); - } + public boolean collectionExists(String collection) { + return getDatabase().listCollectionNames().into(new ArrayList<>()).contains(collection); } /** * Selects all documents provided in a collection and passes them to the consumer. * - * @param database The database to select from. * @param collection The collection to select from. * @param consumer The consumer to pass the documents to. */ - public void selectAllFromCollection(String database, String collection, Consumer consumer) { - try (MongoClient client = connect()) { - MongoCollection mongoCollection = client.getDatabase(database).getCollection(collection); - mongoCollection.find().forEach(consumer); - } + public void selectAllFromCollection(String collection, Consumer consumer) { + MongoCollection mongoCollection = getDatabase().getCollection(collection); + mongoCollection.find().forEach(consumer); + } + + /** + * Inserts a document into a collection. + * + * @param collection The collection to insert into. + * @param document The document to insert. + */ + public void insertOne(String collection, Document document) { + MongoCollection mongoCollection = getDatabase().getCollection(collection); + mongoCollection.insertOne(document); } + + } diff --git a/src/main/java/us/mytheria/bloblib/storage/StorageType.java b/src/main/java/us/mytheria/bloblib/storage/StorageType.java new file mode 100644 index 00000000..e4bb629b --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/storage/StorageType.java @@ -0,0 +1,7 @@ +package us.mytheria.bloblib.storage; + +public enum StorageType { + SQLITE, + MYSQL, + MONGODB +} diff --git a/src/main/java/us/mytheria/bloblib/utilities/BlobCrudManagerBuilder.java b/src/main/java/us/mytheria/bloblib/utilities/BlobCrudManagerBuilder.java index eefcce22..28ce3222 100644 --- a/src/main/java/us/mytheria/bloblib/utilities/BlobCrudManagerBuilder.java +++ b/src/main/java/us/mytheria/bloblib/utilities/BlobCrudManagerBuilder.java @@ -5,13 +5,119 @@ import me.anjoismysign.anjo.crud.MySQLCrudManager; import me.anjoismysign.anjo.crud.SQLiteCrudManager; import me.anjoismysign.anjo.entities.NamingConventions; +import me.anjoismysign.anjo.entities.Result; import org.bukkit.configuration.ConfigurationSection; +import us.mytheria.bloblib.entities.BlobCrudable; +import us.mytheria.bloblib.exception.ConfigurationFieldException; import us.mytheria.bloblib.managers.BlobPlugin; +import us.mytheria.bloblib.storage.*; import java.util.UUID; import java.util.function.Function; +/** + * Misleading class name, use BlobCrudManagerFactory instead + */ +@Deprecated public class BlobCrudManagerBuilder { + public static BlobCrudManager PLAYER(BlobPlugin plugin, + String crudableName, + Function createFunction, + boolean logActivity) { + IdentifierType identifierType; + String type = plugin.getConfig().getString("IdentifierType", "UUID"); + try { + identifierType = IdentifierType.valueOf(type); + } catch (IllegalArgumentException e) { + plugin.getLogger().warning("Invalid IdentifierType '" + type + "'. Defaulting to UUID."); + identifierType = IdentifierType.UUID; + } + switch (identifierType) { + case UUID -> { + return UUID(plugin, crudableName, uuid -> { + BlobCrudable crudable = new BlobCrudable(uuid.toString()); + return createFunction.apply(crudable); + }, logActivity); + } + case PLAYERNAME -> { + return PLAYERNAME(plugin, crudableName, playerName -> { + BlobCrudable crudable = new BlobCrudable(playerName); + return createFunction.apply(crudable); + }, logActivity); + } + default -> throw new IllegalArgumentException("Invalid IdentifierType '" + identifierType + "'."); + } + } + + //Misleading class name, use BlobCrudManagerFactory instead + @Deprecated + public static BlobCrudManager UUID(BlobPlugin plugin, + String crudableName, Function createFunction, + boolean logActivity) { + ConfigurationSection databaseSection = plugin.getConfig().getConfigurationSection("Database"); + StorageType storageType; + String type = databaseSection.getString("Type", "SQLITE"); + try { + storageType = StorageType.valueOf(type); + } catch (IllegalArgumentException e) { + plugin.getLogger().warning("Invalid StorageType '" + type + "'. Defaulting to SQLITE."); + storageType = StorageType.SQLITE; + } + switch (storageType) { + case MYSQL -> { + return new BlobCrudManager<>(MYSQL(plugin, "UUID", 36, + crudableName, string -> { + UUID uuid = UUID.fromString(string); + return createFunction.apply(uuid); + }, logActivity), StorageType.MYSQL, IdentifierType.UUID); + } + case SQLITE -> { + return new BlobCrudManager<>(SQLITE(plugin, "UUID", 36, + crudableName, string -> { + UUID uuid = UUID.fromString(string); + return createFunction.apply(uuid); + }, logActivity), StorageType.SQLITE, IdentifierType.UUID); + } + case MONGODB -> { + return new BlobCrudManager<>(MONGO(plugin, crudableName, string -> { + UUID uuid = UUID.fromString(string); + return createFunction.apply(uuid); + }, logActivity), StorageType.MONGODB, IdentifierType.UUID); + } + default -> throw new IllegalArgumentException("Invalid StorageType '" + storageType + "'."); + } + } + + //Misleading class name, use BlobCrudManagerFactory instead + @Deprecated + public static BlobCrudManager PLAYERNAME(BlobPlugin plugin, + String crudableName, Function createFunction, + boolean logActivity) { + ConfigurationSection databaseSection = plugin.getConfig().getConfigurationSection("Database"); + StorageType storageType; + String type = databaseSection.getString("Type", "SQLITE"); + try { + storageType = StorageType.valueOf(type); + } catch (IllegalArgumentException e) { + plugin.getLogger().warning("Invalid StorageType '" + type + "'. Defaulting to SQLITE."); + storageType = StorageType.SQLITE; + } + switch (storageType) { + case MYSQL -> { + return new BlobCrudManager<>(MYSQL(plugin, "USERNAME", 16, + crudableName, createFunction, logActivity), StorageType.MYSQL, IdentifierType.PLAYERNAME); + } + case SQLITE -> { + return new BlobCrudManager<>(SQLITE(plugin, "USERNAME", 16, + crudableName, createFunction, logActivity), StorageType.SQLITE, IdentifierType.PLAYERNAME); + } + case MONGODB -> { + return new BlobCrudManager<>(MONGO(plugin, crudableName, createFunction, logActivity), + StorageType.MONGODB, IdentifierType.PLAYERNAME); + } + default -> throw new IllegalArgumentException("Invalid StorageType '" + storageType + "'."); + } + } /** * Creates a new SQLiteCrudManager. @@ -32,12 +138,17 @@ public class BlobCrudManagerBuilder { * @param logActivity Whether to log activity. * @param The type of crudable. * @return A new SQLiteCrudManager. + * @Deprecated Misleading class name, use BlobCrudManagerFactory instead */ - public static MySQLCrudManager MYSQL(BlobPlugin plugin, - String primaryKeyName, int primaryKeyLength, - String crudableName, Function createFunction, - boolean logActivity) { + @Deprecated + private static MySQLCrudManager MYSQL(BlobPlugin plugin, + String primaryKeyName, int primaryKeyLength, + String crudableName, Function createFunction, + boolean logActivity) { ConfigurationSection databaseSection = plugin.getConfig().getConfigurationSection("Database"); + StorageType storageType = StorageType.valueOf(databaseSection.getString("Type", "SQLITE")); + if (storageType != StorageType.MYSQL) + throw new IllegalArgumentException("StorageType is not MYSQL (" + storageType + ")"); String hostname = databaseSection.getString("Hostname"); int port = databaseSection.getInt("Port"); String database = databaseSection.getString("Database"); @@ -47,58 +158,10 @@ public static MySQLCrudManager MYSQL(BlobPlugin plugin, return CrudManagerBuilder.MYSQL(hostname, port, database, user, password, tableNamingConvention(crudableName), primaryKeyName, primaryKeyLength, crudableName.toUpperCase(), createFunction, plugin.getAnjoLogger()); else - return CrudManagerBuilder.MYSQL(hostname, port, database, user, password, tableNamingConvention(crudableName), primaryKeyName, + return CrudManagerBuilder.MYSQL_NO_LOGGER(hostname, port, database, user, password, tableNamingConvention(crudableName), primaryKeyName, primaryKeyLength, crudableName.toUpperCase(), createFunction); } - public static MySQLCrudManager MYSQL(BlobPlugin plugin, - String primaryKeyName, int primaryKeyLength, - String crudableName, Function createFunction) { - return MYSQL(plugin, primaryKeyName, primaryKeyLength, - crudableName, createFunction, false); - } - - public static MySQLCrudManager MYSQL_UUID(BlobPlugin plugin, - String primaryKeyName, int primaryKeyLength, - String crudableName, Function createFunction, - boolean logActivity) { - return MYSQL(plugin, primaryKeyName, primaryKeyLength, - crudableName, string -> { - UUID uuid = UUID.fromString(string); - return createFunction.apply(uuid); - }, logActivity); - } - - /** - * Creates a new SQLiteCrudManager in which the primary key is a UUID. - * Will attempt to load from the plugin's config file. - * It should have a ConfigurationSection named "Database" - * with the following keys: - * - Hostname (a String) - * - Port (an Integer) - * - Database (a String) - * - Username (a String) - * - Password (a String) - *

- * logActivity is set to false, - * which means it will not log activity. - * related to the database. - * - * @param plugin The plugin to load the config from. - * @param primaryKeyName The name of the primary key. - * @param primaryKeyLength The length of the primary key. - * @param crudableName The name of the crudable. - * @param createFunction The function to create a new instance of the crudable. - * @param The type of crudable. - * @return A new SQLiteCrudManager. - */ - public static MySQLCrudManager MYSQL_UUID(BlobPlugin plugin, - String primaryKeyName, int primaryKeyLength, - String crudableName, Function createFunction) { - return MYSQL_UUID(plugin, primaryKeyName, primaryKeyLength, - crudableName, createFunction, false); - } - /** * Creates a new SQLiteCrudManager. @@ -115,59 +178,59 @@ public static MySQLCrudManager MYSQL_UUID(BlobPlugin plu * @param logActivity Whether to log activity. * @param The type of crudable. * @return A new SQLiteCrudManager. + * @Deprecated Misleading class name, use BlobCrudManagerFactory instead */ - public static SQLiteCrudManager SQLITE(BlobPlugin plugin, - String primaryKeyName, int primaryKeyLength, - String crudableName, Function function, - boolean logActivity) { + @Deprecated + private static SQLiteCrudManager SQLITE(BlobPlugin plugin, + String primaryKeyName, int primaryKeyLength, + String crudableName, Function function, + boolean logActivity) { ConfigurationSection databaseSection = plugin.getConfig().getConfigurationSection("Database"); + StorageType storageType = StorageType.valueOf(databaseSection.getString("Type", "SQLITE")); + if (storageType != StorageType.SQLITE) + throw new IllegalArgumentException("StorageType is not SQLITE (" + storageType + ")"); String database = databaseSection.getString("Database"); if (logActivity) return CrudManagerBuilder.SQLITE(database, plugin.getDataFolder(), tableNamingConvention(crudableName), primaryKeyName, primaryKeyLength, crudableName.toUpperCase(), function, plugin.getAnjoLogger()); else - return CrudManagerBuilder.SQLITE(database, plugin.getDataFolder(), tableNamingConvention(crudableName), primaryKeyName, + return CrudManagerBuilder.SQLITE_NO_LOGGER(database, plugin.getDataFolder(), tableNamingConvention(crudableName), primaryKeyName, primaryKeyLength, crudableName.toUpperCase(), function); } - public static SQLiteCrudManager SQLITE_UUID(BlobPlugin plugin, - String primaryKeyName, int primaryKeyLength, - String crudableName, Function function, - boolean logActivity) { - return SQLITE(plugin, primaryKeyName, primaryKeyLength, crudableName, string -> { - UUID uuid = UUID.fromString(string); - return function.apply(uuid); - }, logActivity); - } - - /** - * Creates a new SQLiteCrudManager in which the primary key is a UUID. - * Will attempt to load from the plugin's config file. - * It should have a ConfigurationSection named "Database" - * with the following key: - * - Database (a String) - *

- * logActivity is set to false, - * which means it will not log activity. - * related to the database. - * - * @param plugin The plugin to load the config from. - * @param primaryKeyName The name of the primary key. - * @param primaryKeyLength The length of the primary key. - * @param crudableName The name of the crudable. - * @param function The function to create a new instance of the crudable. - * @param The type of crudable. - * @return A new SQLiteCrudManager. - */ - public static SQLiteCrudManager SQLITE_UUID(BlobPlugin plugin, - String primaryKeyName, int primaryKeyLength, - String crudableName, Function function) { - return SQLITE_UUID(plugin, primaryKeyName, primaryKeyLength, crudableName, function, false); - } - private static String tableNamingConvention(String name) { name = NamingConventions.toPascalCase(name); name = "tbl" + name + "s"; return name; } + + private static MongoCrudManager MONGO(BlobPlugin plugin, + String crudableName, Function createFunction, + boolean logActivity) { + if (!plugin.getConfig().isConfigurationSection("Database")) + throw new IllegalArgumentException("Database section not found"); + ConfigurationSection databaseSection = plugin.getConfig().getConfigurationSection("Database"); + StorageType storageType = StorageType.valueOf(databaseSection.getString("Type", "SQLITE")); + if (storageType != StorageType.MONGODB) + throw new IllegalArgumentException("StorageType is not MONGODB (" + storageType + ")"); + MongoDB mongoDB; + if (databaseSection.isString("URI")) { + String uri = databaseSection.getString("URI"); + if (!databaseSection.isString("Database")) + throw new ConfigurationFieldException("'Database' field not found"); + String database = databaseSection.getString("Database"); + mongoDB = MongoDB.fromURI(uri, database); + } else { + Result result = MongoDB.fromConfigurationSection(databaseSection); + if (!result.isValid()) + throw new IllegalArgumentException("Invalid MongoDB configuration"); + mongoDB = result.value(); + } + if (logActivity) + return new MongoCrudManager<>(mongoDB, NamingConventions.toSnakeCase(crudableName), + createFunction, plugin.getAnjoLogger()); + else + return new MongoCrudManager<>(mongoDB, NamingConventions.toSnakeCase(crudableName), + createFunction, null); + } } diff --git a/src/main/java/us/mytheria/bloblib/utilities/BlobCrudManagerFactory.java b/src/main/java/us/mytheria/bloblib/utilities/BlobCrudManagerFactory.java new file mode 100644 index 00000000..bf7632d5 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/utilities/BlobCrudManagerFactory.java @@ -0,0 +1,244 @@ +package us.mytheria.bloblib.utilities; + +import me.anjoismysign.anjo.crud.CrudManagerBuilder; +import me.anjoismysign.anjo.crud.Crudable; +import me.anjoismysign.anjo.crud.MySQLCrudManager; +import me.anjoismysign.anjo.crud.SQLiteCrudManager; +import me.anjoismysign.anjo.entities.NamingConventions; +import me.anjoismysign.anjo.entities.Result; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import us.mytheria.bloblib.entities.BlobCrudable; +import us.mytheria.bloblib.exception.ConfigurationFieldException; +import us.mytheria.bloblib.managers.BlobPlugin; +import us.mytheria.bloblib.storage.*; + +import java.util.UUID; +import java.util.function.Function; + +/** + * It will create a new BlobCrudManager for you. + * The easiest way to use this is to call the PLAYER method. + */ +public class BlobCrudManagerFactory { + public static BlobCrudManager PLAYER(BlobPlugin plugin, + String crudableName, + Function createFunction, + boolean logActivity) { + IdentifierType identifierType; + String type = plugin.getConfig().getString("IdentifierType", "UUID"); + try { + identifierType = IdentifierType.valueOf(type); + } catch (IllegalArgumentException e) { + plugin.getLogger().warning("Invalid IdentifierType '" + type + "'. Defaulting to UUID."); + identifierType = IdentifierType.UUID; + } + FileConfiguration config = plugin.getConfig(); + if (!config.isConfigurationSection("Database")) { + throw new IllegalArgumentException("Invalid database configuration. Missing 'Database'."); + } + ConfigurationSection databaseSection = config.getConfigurationSection("Database"); + if (!databaseSection.isString("Hostname")) + throw new IllegalArgumentException("Invalid database configuration. Missing 'Database.Hostname'."); + if (!databaseSection.isInt("Port")) + throw new IllegalArgumentException("Invalid database configuration. Missing 'Database.Port'."); + if (!databaseSection.isString("Database")) + throw new IllegalArgumentException("Invalid database configuration. Missing 'Database.Database'."); + if (!databaseSection.isString("Username")) + throw new IllegalArgumentException("Invalid database configuration. Missing 'Database.Username'."); + if (!databaseSection.isString("Password")) + throw new IllegalArgumentException("Invalid database configuration. Missing 'Database.Password'."); + switch (identifierType) { + case UUID -> { + return UUID(plugin, crudableName, uuid -> { + BlobCrudable crudable = new BlobCrudable(uuid.toString()); + return createFunction.apply(crudable); + }, logActivity); + } + case PLAYERNAME -> { + return PLAYERNAME(plugin, crudableName, playerName -> { + BlobCrudable crudable = new BlobCrudable(playerName); + return createFunction.apply(crudable); + }, logActivity); + } + default -> throw new IllegalArgumentException("Invalid IdentifierType '" + identifierType + "'."); + } + } + + public static BlobCrudManager UUID(BlobPlugin plugin, + String crudableName, Function createFunction, + boolean logActivity) { + ConfigurationSection databaseSection = plugin.getConfig().getConfigurationSection("Database"); + StorageType storageType; + String type = databaseSection.getString("Type", "SQLITE"); + try { + storageType = StorageType.valueOf(type); + } catch (IllegalArgumentException e) { + plugin.getLogger().warning("Invalid StorageType '" + type + "'. Defaulting to SQLITE."); + storageType = StorageType.SQLITE; + } + switch (storageType) { + case MYSQL -> { + return new BlobCrudManager<>(MYSQL(plugin, "UUID", 36, + crudableName, string -> { + UUID uuid = UUID.fromString(string); + return createFunction.apply(uuid); + }, logActivity), StorageType.MYSQL, IdentifierType.UUID); + } + case SQLITE -> { + return new BlobCrudManager<>(SQLITE(plugin, "UUID", 36, + crudableName, string -> { + UUID uuid = UUID.fromString(string); + return createFunction.apply(uuid); + }, logActivity), StorageType.SQLITE, IdentifierType.UUID); + } + case MONGODB -> { + return new BlobCrudManager<>(MONGO(plugin, crudableName, string -> { + UUID uuid = UUID.fromString(string); + return createFunction.apply(uuid); + }, logActivity), StorageType.MONGODB, IdentifierType.UUID); + } + default -> throw new IllegalArgumentException("Invalid StorageType '" + storageType + "'."); + } + } + + public static BlobCrudManager PLAYERNAME(BlobPlugin plugin, + String crudableName, Function createFunction, + boolean logActivity) { + ConfigurationSection databaseSection = plugin.getConfig().getConfigurationSection("Database"); + StorageType storageType; + String type = databaseSection.getString("Type", "SQLITE"); + try { + storageType = StorageType.valueOf(type); + } catch (IllegalArgumentException e) { + plugin.getLogger().warning("Invalid StorageType '" + type + "'. Defaulting to SQLITE."); + storageType = StorageType.SQLITE; + } + switch (storageType) { + case MYSQL -> { + return new BlobCrudManager<>(MYSQL(plugin, "USERNAME", 16, + crudableName, createFunction, logActivity), StorageType.MYSQL, IdentifierType.PLAYERNAME); + } + case SQLITE -> { + return new BlobCrudManager<>(SQLITE(plugin, "USERNAME", 16, + crudableName, createFunction, logActivity), StorageType.SQLITE, IdentifierType.PLAYERNAME); + } + case MONGODB -> { + return new BlobCrudManager<>(MONGO(plugin, crudableName, createFunction, logActivity), + StorageType.MONGODB, IdentifierType.PLAYERNAME); + } + default -> throw new IllegalArgumentException("Invalid StorageType '" + storageType + "'."); + } + } + + /** + * Creates a new SQLiteCrudManager. + * Will attempt to load from the plugin's config file. + * It should have a ConfigurationSection named "Database" + * with the following keys: + * - Hostname (a String) + * - Port (an Integer) + * - Database (a String) + * - Username (a String) + * - Password (a String) + * + * @param plugin The plugin to load the config from. + * @param primaryKeyName The name of the primary key. + * @param primaryKeyLength The length of the primary key. + * @param crudableName The name of the crudable. + * @param createFunction The function to create a new instance of the crudable. + * @param logActivity Whether to log activity. + * @param The type of crudable. + * @return A new SQLiteCrudManager. + */ + private static MySQLCrudManager MYSQL(BlobPlugin plugin, + String primaryKeyName, int primaryKeyLength, + String crudableName, Function createFunction, + boolean logActivity) { + ConfigurationSection databaseSection = plugin.getConfig().getConfigurationSection("Database"); + StorageType storageType = StorageType.valueOf(databaseSection.getString("Type", "SQLITE")); + if (storageType != StorageType.MYSQL) + throw new IllegalArgumentException("StorageType is not MYSQL (" + storageType + ")"); + String hostname = databaseSection.getString("Hostname"); + int port = databaseSection.getInt("Port"); + String database = databaseSection.getString("Database"); + String user = databaseSection.getString("Username"); + String password = databaseSection.getString("Password"); + if (logActivity) + return CrudManagerBuilder.MYSQL(hostname, port, database, user, password, tableNamingConvention(crudableName), primaryKeyName, + primaryKeyLength, crudableName.toUpperCase(), createFunction, plugin.getAnjoLogger()); + else + return CrudManagerBuilder.MYSQL_NO_LOGGER(hostname, port, database, user, password, tableNamingConvention(crudableName), primaryKeyName, + primaryKeyLength, crudableName.toUpperCase(), createFunction); + } + + + /** + * Creates a new SQLiteCrudManager. + * Will attempt to load from the plugin's config file. + * It should have a ConfigurationSection named "Database" + * with the following key: + * - Database (a String) + * + * @param plugin The plugin to load the config from. + * @param primaryKeyName The name of the primary key. + * @param primaryKeyLength The length of the primary key. + * @param crudableName The name of the crudable. + * @param function The function to create a new instance of the crudable. + * @param logActivity Whether to log activity. + * @param The type of crudable. + * @return A new SQLiteCrudManager. + */ + private static SQLiteCrudManager SQLITE(BlobPlugin plugin, + String primaryKeyName, int primaryKeyLength, + String crudableName, Function function, + boolean logActivity) { + ConfigurationSection databaseSection = plugin.getConfig().getConfigurationSection("Database"); + StorageType storageType = StorageType.valueOf(databaseSection.getString("Type", "SQLITE")); + if (storageType != StorageType.SQLITE) + throw new IllegalArgumentException("StorageType is not SQLITE (" + storageType + ")"); + String database = databaseSection.getString("Database"); + if (logActivity) + return CrudManagerBuilder.SQLITE(database, plugin.getDataFolder(), tableNamingConvention(crudableName), primaryKeyName, + primaryKeyLength, crudableName.toUpperCase(), function, plugin.getAnjoLogger()); + else + return CrudManagerBuilder.SQLITE_NO_LOGGER(database, plugin.getDataFolder(), tableNamingConvention(crudableName), primaryKeyName, + primaryKeyLength, crudableName.toUpperCase(), function); + } + + private static String tableNamingConvention(String name) { + name = NamingConventions.toPascalCase(name); + name = "tbl" + name + "s"; + return name; + } + + private static MongoCrudManager MONGO(BlobPlugin plugin, + String crudableName, Function createFunction, + boolean logActivity) { + if (!plugin.getConfig().isConfigurationSection("Database")) + throw new IllegalArgumentException("Database section not found"); + ConfigurationSection databaseSection = plugin.getConfig().getConfigurationSection("Database"); + StorageType storageType = StorageType.valueOf(databaseSection.getString("Type", "SQLITE")); + if (storageType != StorageType.MONGODB) + throw new IllegalArgumentException("StorageType is not MONGODB (" + storageType + ")"); + MongoDB mongoDB; + if (databaseSection.isString("URI")) { + String uri = databaseSection.getString("URI"); + if (!databaseSection.isString("Database")) + throw new ConfigurationFieldException("'Database' field not found"); + String database = databaseSection.getString("Database"); + mongoDB = MongoDB.fromURI(uri, database); + } else { + Result result = MongoDB.fromConfigurationSection(databaseSection); + if (!result.isValid()) + throw new IllegalArgumentException("Invalid MongoDB configuration"); + mongoDB = result.value(); + } + if (logActivity) + return new MongoCrudManager<>(mongoDB, NamingConventions.toSnakeCase(crudableName), + createFunction, plugin.getAnjoLogger()); + else + return new MongoCrudManager<>(mongoDB, NamingConventions.toSnakeCase(crudableName), + createFunction, null); + } +} diff --git a/src/main/java/us/mytheria/bloblib/utilities/BlockFaceUtil.java b/src/main/java/us/mytheria/bloblib/utilities/BlockFaceUtil.java new file mode 100644 index 00000000..cad507fe --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/utilities/BlockFaceUtil.java @@ -0,0 +1,75 @@ +package us.mytheria.bloblib.utilities; + +import org.bukkit.block.BlockFace; +import org.bukkit.block.structure.StructureRotation; +import org.jetbrains.annotations.NotNull; + +public class BlockFaceUtil { + + @NotNull + public static BlockFace rotate(BlockFace face, StructureRotation rotation) { + switch (rotation) { + case NONE -> { + return face; + } + case CLOCKWISE_90 -> { + switch (face) { + case NORTH -> { + return BlockFace.EAST; + } + case EAST -> { + return BlockFace.SOUTH; + } + case SOUTH -> { + return BlockFace.WEST; + } + case WEST -> { + return BlockFace.NORTH; + } + default -> { + throw new IllegalArgumentException("Invalid BlockFace: " + face); + } + } + } + case CLOCKWISE_180 -> { + switch (face) { + case NORTH -> { + return BlockFace.SOUTH; + } + case EAST -> { + return BlockFace.WEST; + } + case SOUTH -> { + return BlockFace.NORTH; + } + case WEST -> { + return BlockFace.EAST; + } + default -> { + throw new IllegalArgumentException("Invalid BlockFace: " + face); + } + } + } + case COUNTERCLOCKWISE_90 -> { + switch (face) { + case NORTH -> { + return BlockFace.WEST; + } + case EAST -> { + return BlockFace.NORTH; + } + case SOUTH -> { + return BlockFace.EAST; + } + case WEST -> { + return BlockFace.SOUTH; + } + default -> { + throw new IllegalArgumentException("Invalid BlockFace: " + face); + } + } + } + } + throw new IllegalArgumentException("Invalid StructureRotation: " + rotation); + } +} diff --git a/src/main/java/us/mytheria/bloblib/utilities/BukkitUtil.java b/src/main/java/us/mytheria/bloblib/utilities/BukkitUtil.java index 8010ceb9..fd8d9963 100644 --- a/src/main/java/us/mytheria/bloblib/utilities/BukkitUtil.java +++ b/src/main/java/us/mytheria/bloblib/utilities/BukkitUtil.java @@ -2,6 +2,7 @@ import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.YamlConfiguration; @@ -62,6 +63,24 @@ public static ConfigurationSection serializeLocation(Location location, YamlConf return section; } + @Nullable + public static Location deserializeLocationOrNull(ConfigurationSection section) { + if (!section.isDouble("X") || !section.isDouble("Y") || !section.isDouble("Z") || !section.isString("World")) + return null; + double x = section.getDouble("X"); + double y = section.getDouble("Y"); + double z = section.getDouble("Z"); + float yaw = (float) section.getDouble("Yaw", 0.0); + float pitch = (float) section.getDouble("Pitch", 0.0); + World world; + try { + world = SerializationLib.deserializeWorld(section.getString("World")); + } catch (Exception e) { + return null; + } + return new Location(world, x, y, z, yaw, pitch); + } + public static Location deserializeLocation(ConfigurationSection section) { return new Location(Bukkit.getWorld(section.getString("World")), section.getDouble("X"), section.getDouble("Y"), section.getDouble("Z"), (float) section.getDouble("Yaw"), diff --git a/src/main/java/us/mytheria/bloblib/utilities/ConfigurationSectionLib.java b/src/main/java/us/mytheria/bloblib/utilities/ConfigurationSectionLib.java index e9f01cfa..6b6cfd2d 100644 --- a/src/main/java/us/mytheria/bloblib/utilities/ConfigurationSectionLib.java +++ b/src/main/java/us/mytheria/bloblib/utilities/ConfigurationSectionLib.java @@ -262,7 +262,6 @@ public static List deserializeVectorList(ConfigurationSection section, S List test = new ArrayList<>(); List vectorList = new ArrayList<>(); section.getStringList(path).forEach(string -> { - String[] split = string.split(", "); vectorList.add(SerializationLib.deserializeVector(string)); }); return vectorList; @@ -312,7 +311,6 @@ public static void serializeBlockVectorList(List list, Configuratio public static List deserializeBlockVectorList(ConfigurationSection section, String path) { List vectorList = new ArrayList<>(); section.getStringList(path).forEach(string -> { - String[] split = string.split(", "); vectorList.add(SerializationLib.deserializeBlockVector(string)); }); return vectorList; @@ -362,7 +360,6 @@ public static void serializeLocationList(List list, ConfigurationSecti public static List deserializeLocationList(ConfigurationSection section, String path) { List locationList = new ArrayList<>(); section.getStringList(path).forEach(string -> { - String[] split = string.split(", "); locationList.add(SerializationLib.deserializeLocation(string)); }); return locationList; @@ -412,7 +409,6 @@ public static void serializeBlockList(List list, ConfigurationSection sec public static List deserializeBlockList(ConfigurationSection section, String path) { List blockList = new ArrayList<>(); section.getStringList(path).forEach(string -> { - String[] split = string.split(", "); blockList.add(SerializationLib.deserializeBlock(string)); }); return blockList; @@ -462,7 +458,6 @@ public static void serializeColorList(List list, ConfigurationSection sec public static List deserializeColorList(ConfigurationSection section, String path) { List colorList = new ArrayList<>(); section.getStringList(path).forEach(string -> { - String[] split = string.split(", "); colorList.add(SerializationLib.deserializeColor(string)); }); return colorList; diff --git a/src/main/java/us/mytheria/bloblib/utilities/DoubleUtil.java b/src/main/java/us/mytheria/bloblib/utilities/DoubleUtil.java new file mode 100644 index 00000000..cdcb1745 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/utilities/DoubleUtil.java @@ -0,0 +1,7 @@ +package us.mytheria.bloblib.utilities; + +public class DoubleUtil { + public static double invertSign(double doubleToInvert) { + return doubleToInvert * -1; + } +} diff --git a/src/main/java/us/mytheria/bloblib/utilities/Enumaterpretrer.java b/src/main/java/us/mytheria/bloblib/utilities/Enumaterpretrer.java new file mode 100644 index 00000000..3716e4db --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/utilities/Enumaterpretrer.java @@ -0,0 +1,97 @@ +package us.mytheria.bloblib.utilities; + +import us.mytheria.bloblib.exception.InterpretationException; + +import java.util.Collection; +import java.util.EnumSet; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Interpreter for enum values. + */ +public class Enumaterpretrer { + + public static > Set PARSE(Class enumClass, String... strings) { + return new Enumaterpretrer().parse(Stream.of(strings), enumClass); + } + + public static > Set PARSE(Class enumClass, Collection strings) { + return new Enumaterpretrer().parse(strings.stream(), enumClass); + } + + private enum ParseOperations { + NOT_EQUALS(), + STARTS_WITH(), + ENDS_WITH(), + CONTAINS(), + NOT_STARTS_WITH(), + NOT_ENDS_WITH(), + NOT_CONTAINS() + } + + private > Set parse(Stream stream, Class enumClass) { + EnumSet allOf = EnumSet.allOf(enumClass); + return stream.map(s -> parseOperation(s, enumClass)) + .flatMap(operation -> applyOperation(allOf, operation)) + .collect(Collectors.toSet()); + } + + private > Operation parseOperation(String s, Class enumClass) { + if (s.contains("/")) { + String[] split = s.split("/"); + if (split.length == 1) + throw InterpretationException.INVALID_INPUT(s); + try { + ParseOperations operation = ParseOperations.valueOf(split[0]); + String enumName = split[1]; + E enumValue = Enum.valueOf(enumClass, enumName); + return new Operation<>(operation, enumValue); + } catch (IllegalArgumentException e) { + throw InterpretationException.INVALID_OPERATION(s); + } + } else { + E enumValue = Enum.valueOf(enumClass, s); + return new Operation<>(null, enumValue); + } + } + + private > Stream applyOperation(EnumSet allOf, Operation operation) { + if (operation.operation == null) { + return Stream.of(operation.enumValue); + } + + return allOf.stream().filter(enumValue -> { + switch (operation.operation) { + case NOT_EQUALS: + return enumValue != operation.enumValue; + case STARTS_WITH: + return enumValue.name().startsWith(operation.enumValue.name()); + case ENDS_WITH: + return enumValue.name().endsWith(operation.enumValue.name()); + case CONTAINS: + return enumValue.name().contains(operation.enumValue.name()); + case NOT_STARTS_WITH: + return !enumValue.name().startsWith(operation.enumValue.name()); + case NOT_ENDS_WITH: + return !enumValue.name().endsWith(operation.enumValue.name()); + case NOT_CONTAINS: + return !enumValue.name().contains(operation.enumValue.name()); + default: + return false; + } + }); + } + + private static class Operation> { + private final ParseOperations operation; + private final E enumValue; + + public Operation(ParseOperations operation, E enumValue) { + this.operation = operation; + this.enumValue = enumValue; + } + } + +} diff --git a/src/main/java/us/mytheria/bloblib/utilities/Formatter.java b/src/main/java/us/mytheria/bloblib/utilities/Formatter.java index 5a0fd9c3..e52b1fbb 100644 --- a/src/main/java/us/mytheria/bloblib/utilities/Formatter.java +++ b/src/main/java/us/mytheria/bloblib/utilities/Formatter.java @@ -16,10 +16,10 @@ public static String WATTS(float value) { } public static String BYTES(float value) { - String[] arr = {"", "K", "M", "G", "T", "P", "E", "Z", "Y"}; + String[] arr = {"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"}; int index = 0; - while ((value / 1000) >= 1) { - value = value / 1000; + while ((value / 1024) >= 1) { + value = value / 1024; index++; } DecimalFormat decimalFormat = new DecimalFormat("#.##"); diff --git a/src/main/java/us/mytheria/bloblib/utilities/ItemStackUtil.java b/src/main/java/us/mytheria/bloblib/utilities/ItemStackUtil.java index b0fc356d..2b6dd9a9 100644 --- a/src/main/java/us/mytheria/bloblib/utilities/ItemStackUtil.java +++ b/src/main/java/us/mytheria/bloblib/utilities/ItemStackUtil.java @@ -10,9 +10,18 @@ import javax.annotation.Nullable; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.util.ArrayList; import java.util.Base64; +import java.util.List; public class ItemStackUtil { + + /** + * Will display an ItemStack by either their ItemMeta's displayname or their Material's name + * + * @param itemStack The ItemStack to display + * @return The displayname of the ItemStack + */ public static String display(ItemStack itemStack) { if (itemStack == null) return "null"; @@ -21,6 +30,42 @@ public static String display(ItemStack itemStack) { return itemStack.getType().name(); } + /** + * Will replace all instances of a string in an ItemStack's ItemMeta + * + * @param itemStack The ItemStack to replace in + * @param target The string to replace + * @param replacement The string to replace with + */ + public static void replace(ItemStack itemStack, String target, String replacement) { + if (itemStack == null) + return; + if (!itemStack.hasItemMeta()) + return; + ItemMeta itemMeta = itemStack.getItemMeta(); + if (itemMeta.hasDisplayName()) { + String displayname = itemMeta.getDisplayName().replace(target, replacement); + itemMeta.setDisplayName(displayname); + } + if (itemMeta.hasLore()) { + List lore = new ArrayList<>(); + List current = itemMeta.getLore(); + for (String s : current) { + lore.add(s.replace(target, replacement)); + } + itemMeta.setLore(lore); + } + itemStack.setItemMeta(itemMeta); + } + + /** + * Will serialize an ItemStack into a string + * + * @param itemStack The ItemStack to serialize + * @return The serialized ItemStack + * @deprecated Wastes a lot of space + */ + @Deprecated @Nullable public static String serialize(ItemStack itemStack) { if (itemStack == null) @@ -35,6 +80,14 @@ public static String serialize(ItemStack itemStack) { return toReturn; } + /** + * Will deserialize a string into an ItemStack + * + * @param serialized The serialized ItemStack + * @return The deserialized ItemStack + * @deprecated Wastes a lot of space + */ + @Deprecated public static ItemStack deserialize(String serialized) { if (serialized.equals("null")) return null; diff --git a/src/main/java/us/mytheria/bloblib/utilities/MaterialUtil.java b/src/main/java/us/mytheria/bloblib/utilities/MaterialUtil.java index d9f4368b..806ce2e1 100644 --- a/src/main/java/us/mytheria/bloblib/utilities/MaterialUtil.java +++ b/src/main/java/us/mytheria/bloblib/utilities/MaterialUtil.java @@ -4,252 +4,299 @@ import org.bukkit.entity.EntityType; import java.util.HashMap; +import java.util.Map; public class MaterialUtil { + private static final Map nonItem = new HashMap<>(); - public static void fillNonItem(HashMap map) { - map.put(Material.WATER, Material.WATER_BUCKET); - map.put(Material.LAVA, Material.LAVA_BUCKET); - map.put(Material.TALL_SEAGRASS, Material.SEAGRASS); - map.put(Material.PISTON_HEAD, Material.PISTON); - map.put(Material.MOVING_PISTON, Material.PISTON); - map.put(Material.WALL_TORCH, Material.TORCH); - map.put(Material.FIRE, Material.CAMPFIRE); - map.put(Material.SOUL_FIRE, Material.SOUL_CAMPFIRE); - map.put(Material.REDSTONE_WIRE, Material.REDSTONE); - map.put(Material.OAK_WALL_SIGN, Material.OAK_SIGN); - map.put(Material.SPRUCE_WALL_SIGN, Material.SPRUCE_SIGN); - map.put(Material.BIRCH_WALL_SIGN, Material.BIRCH_SIGN); - map.put(Material.ACACIA_WALL_SIGN, Material.ACACIA_SIGN); - map.put(Material.JUNGLE_WALL_SIGN, Material.JUNGLE_SIGN); - map.put(Material.DARK_OAK_WALL_SIGN, Material.DARK_OAK_SIGN); - map.put(Material.MANGROVE_WALL_SIGN, Material.MANGROVE_SIGN); - map.put(Material.REDSTONE_WALL_TORCH, Material.REDSTONE_TORCH); - map.put(Material.SOUL_WALL_TORCH, Material.SOUL_TORCH); - map.put(Material.NETHER_PORTAL, Material.OBSIDIAN); - map.put(Material.ATTACHED_PUMPKIN_STEM, Material.PUMPKIN); - map.put(Material.ATTACHED_MELON_STEM, Material.MELON); - map.put(Material.PUMPKIN_STEM, Material.PUMPKIN); - map.put(Material.MELON_STEM, Material.MELON); - map.put(Material.WATER_CAULDRON, Material.CAULDRON); - map.put(Material.LAVA_CAULDRON, Material.CAULDRON); - map.put(Material.POWDER_SNOW_CAULDRON, Material.CAULDRON); - map.put(Material.END_PORTAL, Material.END_PORTAL_FRAME); - map.put(Material.COCOA, Material.COCOA_BEANS); - map.put(Material.TRIPWIRE, Material.STRING); - map.put(Material.POTTED_OAK_SAPLING, Material.FLOWER_POT); - map.put(Material.POTTED_SPRUCE_SAPLING, Material.FLOWER_POT); - map.put(Material.POTTED_BIRCH_SAPLING, Material.FLOWER_POT); - map.put(Material.POTTED_JUNGLE_SAPLING, Material.FLOWER_POT); - map.put(Material.POTTED_ACACIA_SAPLING, Material.FLOWER_POT); - map.put(Material.POTTED_DARK_OAK_SAPLING, Material.FLOWER_POT); - map.put(Material.POTTED_MANGROVE_PROPAGULE, Material.FLOWER_POT); - map.put(Material.POTTED_FERN, Material.FLOWER_POT); - map.put(Material.POTTED_DANDELION, Material.FLOWER_POT); - map.put(Material.POTTED_POPPY, Material.FLOWER_POT); - map.put(Material.POTTED_BLUE_ORCHID, Material.FLOWER_POT); - map.put(Material.POTTED_ALLIUM, Material.FLOWER_POT); - map.put(Material.POTTED_AZURE_BLUET, Material.FLOWER_POT); - map.put(Material.POTTED_RED_TULIP, Material.FLOWER_POT); - map.put(Material.POTTED_ORANGE_TULIP, Material.FLOWER_POT); - map.put(Material.POTTED_WHITE_TULIP, Material.FLOWER_POT); - map.put(Material.POTTED_PINK_TULIP, Material.FLOWER_POT); - map.put(Material.POTTED_OXEYE_DAISY, Material.FLOWER_POT); - map.put(Material.POTTED_CORNFLOWER, Material.FLOWER_POT); - map.put(Material.POTTED_LILY_OF_THE_VALLEY, Material.FLOWER_POT); - map.put(Material.POTTED_WITHER_ROSE, Material.FLOWER_POT); - map.put(Material.POTTED_RED_MUSHROOM, Material.FLOWER_POT); - map.put(Material.POTTED_BROWN_MUSHROOM, Material.FLOWER_POT); - map.put(Material.POTTED_DEAD_BUSH, Material.FLOWER_POT); - map.put(Material.POTTED_CACTUS, Material.FLOWER_POT); - map.put(Material.CARROTS, Material.CARROT); - map.put(Material.POTATOES, Material.POTATO); - map.put(Material.SKELETON_WALL_SKULL, Material.SKELETON_SKULL); - map.put(Material.WITHER_SKELETON_WALL_SKULL, Material.WITHER_SKELETON_SKULL); - map.put(Material.ZOMBIE_WALL_HEAD, Material.ZOMBIE_HEAD); - map.put(Material.PLAYER_WALL_HEAD, Material.PLAYER_HEAD); - map.put(Material.CREEPER_WALL_HEAD, Material.CREEPER_HEAD); - map.put(Material.DRAGON_WALL_HEAD, Material.DRAGON_HEAD); - map.put(Material.WHITE_WALL_BANNER, Material.WHITE_BANNER); - map.put(Material.ORANGE_WALL_BANNER, Material.ORANGE_BANNER); - map.put(Material.MAGENTA_WALL_BANNER, Material.MAGENTA_BANNER); - map.put(Material.LIGHT_BLUE_WALL_BANNER, Material.LIGHT_BLUE_BANNER); - map.put(Material.YELLOW_WALL_BANNER, Material.YELLOW_BANNER); - map.put(Material.LIME_WALL_BANNER, Material.LIME_BANNER); - map.put(Material.PINK_WALL_BANNER, Material.PINK_BANNER); - map.put(Material.GRAY_WALL_BANNER, Material.GRAY_BANNER); - map.put(Material.LIGHT_GRAY_WALL_BANNER, Material.LIGHT_GRAY_BANNER); - map.put(Material.CYAN_WALL_BANNER, Material.CYAN_BANNER); - map.put(Material.PURPLE_WALL_BANNER, Material.PURPLE_BANNER); - map.put(Material.BLUE_WALL_BANNER, Material.BLUE_BANNER); - map.put(Material.BROWN_WALL_BANNER, Material.BROWN_BANNER); - map.put(Material.GREEN_WALL_BANNER, Material.GREEN_BANNER); - map.put(Material.RED_WALL_BANNER, Material.RED_BANNER); - map.put(Material.BLACK_WALL_BANNER, Material.BLACK_BANNER); - map.put(Material.BEETROOTS, Material.BEETROOT); - map.put(Material.END_GATEWAY, Material.END_PORTAL_FRAME); - map.put(Material.FROSTED_ICE, Material.ICE); - map.put(Material.KELP_PLANT, Material.KELP); - map.put(Material.DEAD_TUBE_CORAL_WALL_FAN, Material.DEAD_TUBE_CORAL_FAN); - map.put(Material.DEAD_BRAIN_CORAL_WALL_FAN, Material.DEAD_BRAIN_CORAL_FAN); - map.put(Material.DEAD_BUBBLE_CORAL_WALL_FAN, Material.DEAD_BUBBLE_CORAL_FAN); - map.put(Material.DEAD_FIRE_CORAL_WALL_FAN, Material.DEAD_FIRE_CORAL_FAN); - map.put(Material.DEAD_HORN_CORAL_WALL_FAN, Material.DEAD_HORN_CORAL_FAN); - map.put(Material.TUBE_CORAL_WALL_FAN, Material.TUBE_CORAL_FAN); - map.put(Material.BRAIN_CORAL_WALL_FAN, Material.BRAIN_CORAL_FAN); - map.put(Material.BUBBLE_CORAL_WALL_FAN, Material.BUBBLE_CORAL_FAN); - map.put(Material.FIRE_CORAL_WALL_FAN, Material.FIRE_CORAL_FAN); - map.put(Material.HORN_CORAL_WALL_FAN, Material.HORN_CORAL_FAN); - map.put(Material.BAMBOO_SAPLING, Material.BAMBOO); - map.put(Material.POTTED_BAMBOO, Material.FLOWER_POT); - map.put(Material.VOID_AIR, Material.GLASS); - map.put(Material.CAVE_AIR, Material.GLASS); - map.put(Material.BUBBLE_COLUMN, Material.WATER_BUCKET); - map.put(Material.SWEET_BERRY_BUSH, Material.SWEET_BERRIES); - map.put(Material.WEEPING_VINES_PLANT, Material.WEEPING_VINES); - map.put(Material.TWISTING_VINES_PLANT, Material.TWISTING_VINES); - map.put(Material.CRIMSON_WALL_SIGN, Material.CRIMSON_SIGN); - map.put(Material.WARPED_WALL_SIGN, Material.WARPED_SIGN); - map.put(Material.POTTED_CRIMSON_FUNGUS, Material.FLOWER_POT); - map.put(Material.POTTED_WARPED_FUNGUS, Material.FLOWER_POT); - map.put(Material.POTTED_CRIMSON_ROOTS, Material.FLOWER_POT); - map.put(Material.POTTED_WARPED_ROOTS, Material.FLOWER_POT); - map.put(Material.CANDLE_CAKE, Material.CANDLE); - map.put(Material.WHITE_CANDLE_CAKE, Material.WHITE_CANDLE); - map.put(Material.ORANGE_CANDLE_CAKE, Material.ORANGE_CANDLE); - map.put(Material.MAGENTA_CANDLE_CAKE, Material.MAGENTA_CANDLE); - map.put(Material.LIGHT_BLUE_CANDLE_CAKE, Material.LIGHT_BLUE_CANDLE); - map.put(Material.YELLOW_CANDLE_CAKE, Material.YELLOW_CANDLE); - map.put(Material.LIME_CANDLE_CAKE, Material.LIME_CANDLE); - map.put(Material.PINK_CANDLE_CAKE, Material.PINK_CANDLE); - map.put(Material.GRAY_CANDLE_CAKE, Material.GRAY_CANDLE); - map.put(Material.LIGHT_GRAY_CANDLE_CAKE, Material.LIGHT_GRAY_CANDLE); - map.put(Material.CYAN_CANDLE_CAKE, Material.CYAN_CANDLE); - map.put(Material.PURPLE_CANDLE_CAKE, Material.PURPLE_CANDLE); - map.put(Material.BLUE_CANDLE_CAKE, Material.BLUE_CANDLE); - map.put(Material.BROWN_CANDLE_CAKE, Material.BROWN_CANDLE); - map.put(Material.GREEN_CANDLE_CAKE, Material.GREEN_CANDLE); - map.put(Material.RED_CANDLE_CAKE, Material.RED_CANDLE); - map.put(Material.BLACK_CANDLE_CAKE, Material.BLACK_CANDLE); - map.put(Material.POWDER_SNOW, Material.SNOWBALL); - map.put(Material.CAVE_VINES, Material.GLOW_BERRIES); - map.put(Material.CAVE_VINES_PLANT, Material.GLOW_BERRIES); - map.put(Material.BIG_DRIPLEAF_STEM, Material.BIG_DRIPLEAF); - map.put(Material.POTTED_AZALEA_BUSH, Material.FLOWER_POT); - map.put(Material.POTTED_FLOWERING_AZALEA_BUSH, Material.FLOWER_POT); + private static final Map entityToMaterial = new HashMap<>(); + + static { + safeAddMaterial(nonItem, "WATER", "WATER_BUCKET"); + safeAddMaterial(nonItem, "LAVA", "LAVA_BUCKET"); + safeAddMaterial(nonItem, "TALL_SEAGRASS", "SEAGRASS"); + safeAddMaterial(nonItem, "PISTON_HEAD", "PISTON"); + safeAddMaterial(nonItem, "MOVING_PISTON", "PISTON"); + safeAddMaterial(nonItem, "WALL_TORCH", "TORCH"); + safeAddMaterial(nonItem, "FIRE", "CAMPFIRE"); + safeAddMaterial(nonItem, "SOUL_FIRE", "SOUL_CAMPFIRE"); + safeAddMaterial(nonItem, "REDSTONE_WIRE", "REDSTONE"); + safeAddMaterial(nonItem, "OAK_WALL_SIGN", "OAK_SIGN"); + safeAddMaterial(nonItem, "SPRUCE_WALL_SIGN", "SPRUCE_SIGN"); + safeAddMaterial(nonItem, "BIRCH_WALL_SIGN", "BIRCH_SIGN"); + safeAddMaterial(nonItem, "ACACIA_WALL_SIGN", "ACACIA_SIGN"); + safeAddMaterial(nonItem, "JUNGLE_WALL_SIGN", "JUNGLE_SIGN"); + safeAddMaterial(nonItem, "DARK_OAK_WALL_SIGN", "DARK_OAK_SIGN"); + safeAddMaterial(nonItem, "MANGROVE_WALL_SIGN", "MANGROVE_SIGN"); + safeAddMaterial(nonItem, "REDSTONE_WALL_TORCH", "REDSTONE_TORCH"); + safeAddMaterial(nonItem, "SOUL_WALL_TORCH", "SOUL_TORCH"); + safeAddMaterial(nonItem, "NETHER_PORTAL", "OBSIDIAN"); + safeAddMaterial(nonItem, "ATTACHED_PUMPKIN_STEM", "PUMPKIN"); + safeAddMaterial(nonItem, "ATTACHED_MELON_STEM", "MELON"); + safeAddMaterial(nonItem, "PUMPKIN_STEM", "PUMPKIN"); + safeAddMaterial(nonItem, "MELON_STEM", "MELON"); + safeAddMaterial(nonItem, "WATER_CAULDRON", "CAULDRON"); + safeAddMaterial(nonItem, "LAVA_CAULDRON", "CAULDRON"); + safeAddMaterial(nonItem, "POWDER_SNOW_CAULDRON", "CAULDRON"); + safeAddMaterial(nonItem, "END_PORTAL", "END_PORTAL_FRAME"); + safeAddMaterial(nonItem, "COCOA", "COCOA_BEANS"); + safeAddMaterial(nonItem, "TRIPWIRE", "STRING"); + safeAddMaterial(nonItem, "POTTED_OAK_SAPLING", "FLOWER_POT"); + safeAddMaterial(nonItem, "POTTED_SPRUCE_SAPLING", "FLOWER_POT"); + safeAddMaterial(nonItem, "POTTED_BIRCH_SAPLING", "FLOWER_POT"); + safeAddMaterial(nonItem, "POTTED_JUNGLE_SAPLING", "FLOWER_POT"); + safeAddMaterial(nonItem, "POTTED_ACACIA_SAPLING", "FLOWER_POT"); + safeAddMaterial(nonItem, "POTTED_DARK_OAK_SAPLING", "FLOWER_POT"); + safeAddMaterial(nonItem, "POTTED_MANGROVE_PROPAGULE", "FLOWER_POT"); + safeAddMaterial(nonItem, "POTTED_FERN", "FLOWER_POT"); + safeAddMaterial(nonItem, "POTTED_DANDELION", "FLOWER_POT"); + safeAddMaterial(nonItem, "POTTED_POPPY", "FLOWER_POT"); + safeAddMaterial(nonItem, "POTTED_BLUE_ORCHID", "FLOWER_POT"); + safeAddMaterial(nonItem, "POTTED_ALLIUM", "FLOWER_POT"); + safeAddMaterial(nonItem, "POTTED_AZURE_BLUET", "FLOWER_POT"); + safeAddMaterial(nonItem, "POTTED_RED_TULIP", "FLOWER_POT"); + safeAddMaterial(nonItem, "POTTED_ORANGE_TULIP", "FLOWER_POT"); + safeAddMaterial(nonItem, "POTTED_WHITE_TULIP", "FLOWER_POT"); + safeAddMaterial(nonItem, "POTTED_PINK_TULIP", "FLOWER_POT"); + safeAddMaterial(nonItem, "POTTED_OXEYE_DAISY", "FLOWER_POT"); + safeAddMaterial(nonItem, "POTTED_CORNFLOWER", "FLOWER_POT"); + safeAddMaterial(nonItem, "POTTED_LILY_OF_THE_VALLEY", "FLOWER_POT"); + safeAddMaterial(nonItem, "POTTED_WITHER_ROSE", "FLOWER_POT"); + safeAddMaterial(nonItem, "POTTED_RED_MUSHROOM", "FLOWER_POT"); + safeAddMaterial(nonItem, "POTTED_BROWN_MUSHROOM", "FLOWER_POT"); + safeAddMaterial(nonItem, "POTTED_DEAD_BUSH", "FLOWER_POT"); + safeAddMaterial(nonItem, "POTTED_CACTUS", "FLOWER_POT"); + safeAddMaterial(nonItem, "CARROTS", "CARROT"); + safeAddMaterial(nonItem, "POTATOES", "POTATO"); + safeAddMaterial(nonItem, "SKELETON_WALL_SKULL", "SKELETON_SKULL"); + safeAddMaterial(nonItem, "WITHER_SKELETON_WALL_SKULL", "WITHER_SKELETON_SKULL"); + safeAddMaterial(nonItem, "ZOMBIE_WALL_HEAD", "ZOMBIE_HEAD"); + safeAddMaterial(nonItem, "PLAYER_WALL_HEAD", "PLAYER_HEAD"); + safeAddMaterial(nonItem, "CREEPER_WALL_HEAD", "CREEPER_HEAD"); + safeAddMaterial(nonItem, "DRAGON_WALL_HEAD", "DRAGON_HEAD"); + safeAddMaterial(nonItem, "WHITE_WALL_BANNER", "WHITE_BANNER"); + safeAddMaterial(nonItem, "ORANGE_WALL_BANNER", "ORANGE_BANNER"); + safeAddMaterial(nonItem, "MAGENTA_WALL_BANNER", "MAGENTA_BANNER"); + safeAddMaterial(nonItem, "LIGHT_BLUE_WALL_BANNER", "LIGHT_BLUE_BANNER"); + safeAddMaterial(nonItem, "YELLOW_WALL_BANNER", "YELLOW_BANNER"); + safeAddMaterial(nonItem, "LIME_WALL_BANNER", "LIME_BANNER"); + safeAddMaterial(nonItem, "PINK_WALL_BANNER", "PINK_BANNER"); + safeAddMaterial(nonItem, "GRAY_WALL_BANNER", "GRAY_BANNER"); + safeAddMaterial(nonItem, "LIGHT_GRAY_WALL_BANNER", "LIGHT_GRAY_BANNER"); + safeAddMaterial(nonItem, "CYAN_WALL_BANNER", "CYAN_BANNER"); + safeAddMaterial(nonItem, "PURPLE_WALL_BANNER", "PURPLE_BANNER"); + safeAddMaterial(nonItem, "BLUE_WALL_BANNER", "BLUE_BANNER"); + safeAddMaterial(nonItem, "BROWN_WALL_BANNER", "BROWN_BANNER"); + safeAddMaterial(nonItem, "GREEN_WALL_BANNER", "GREEN_BANNER"); + safeAddMaterial(nonItem, "RED_WALL_BANNER", "RED_BANNER"); + safeAddMaterial(nonItem, "BLACK_WALL_BANNER", "BLACK_BANNER"); + safeAddMaterial(nonItem, "BEETROOTS", "BEETROOT"); + safeAddMaterial(nonItem, "END_GATEWAY", "END_PORTAL_FRAME"); + safeAddMaterial(nonItem, "FROSTED_ICE", "ICE"); + safeAddMaterial(nonItem, "KELP_PLANT", "KELP"); + safeAddMaterial(nonItem, "DEAD_TUBE_CORAL_WALL_FAN", "DEAD_TUBE_CORAL_FAN"); + safeAddMaterial(nonItem, "DEAD_BRAIN_CORAL_WALL_FAN", "DEAD_BRAIN_CORAL_FAN"); + safeAddMaterial(nonItem, "DEAD_BUBBLE_CORAL_WALL_FAN", "DEAD_BUBBLE_CORAL_FAN"); + safeAddMaterial(nonItem, "DEAD_FIRE_CORAL_WALL_FAN", "DEAD_FIRE_CORAL_FAN"); + safeAddMaterial(nonItem, "DEAD_HORN_CORAL_WALL_FAN", "DEAD_HORN_CORAL_FAN"); + safeAddMaterial(nonItem, "TUBE_CORAL_WALL_FAN", "TUBE_CORAL_FAN"); + safeAddMaterial(nonItem, "BRAIN_CORAL_WALL_FAN", "BRAIN_CORAL_FAN"); + safeAddMaterial(nonItem, "BUBBLE_CORAL_WALL_FAN", "BUBBLE_CORAL_FAN"); + safeAddMaterial(nonItem, "FIRE_CORAL_WALL_FAN", "FIRE_CORAL_FAN"); + safeAddMaterial(nonItem, "HORN_CORAL_WALL_FAN", "HORN_CORAL_FAN"); + safeAddMaterial(nonItem, "BAMBOO_SAPLING", "BAMBOO"); + safeAddMaterial(nonItem, "POTTED_BAMBOO", "FLOWER_POT"); + safeAddMaterial(nonItem, "VOID_AIR", "GLASS"); + safeAddMaterial(nonItem, "CAVE_AIR", "GLASS"); + safeAddMaterial(nonItem, "BUBBLE_COLUMN", "WATER_BUCKET"); + safeAddMaterial(nonItem, "SWEET_BERRY_BUSH", "SWEET_BERRIES"); + safeAddMaterial(nonItem, "WEEPING_VINES_PLANT", "WEEPING_VINES"); + safeAddMaterial(nonItem, "TWISTING_VINES_PLANT", "TWISTING_VINES"); + safeAddMaterial(nonItem, "CRIMSON_WALL_SIGN", "CRIMSON_SIGN"); + safeAddMaterial(nonItem, "WARPED_WALL_SIGN", "WARPED_SIGN"); + safeAddMaterial(nonItem, "POTTED_CRIMSON_FUNGUS", "FLOWER_POT"); + safeAddMaterial(nonItem, "POTTED_WARPED_FUNGUS", "FLOWER_POT"); + safeAddMaterial(nonItem, "POTTED_CRIMSON_ROOTS", "FLOWER_POT"); + safeAddMaterial(nonItem, "POTTED_WARPED_ROOTS", "FLOWER_POT"); + safeAddMaterial(nonItem, "CANDLE_CAKE", "CANDLE"); + safeAddMaterial(nonItem, "WHITE_CANDLE_CAKE", "WHITE_CANDLE"); + safeAddMaterial(nonItem, "ORANGE_CANDLE_CAKE", "ORANGE_CANDLE"); + safeAddMaterial(nonItem, "MAGENTA_CANDLE_CAKE", "MAGENTA_CANDLE"); + safeAddMaterial(nonItem, "LIGHT_BLUE_CANDLE_CAKE", "LIGHT_BLUE_CANDLE"); + safeAddMaterial(nonItem, "YELLOW_CANDLE_CAKE", "YELLOW_CANDLE"); + safeAddMaterial(nonItem, "LIME_CANDLE_CAKE", "LIME_CANDLE"); + safeAddMaterial(nonItem, "PINK_CANDLE_CAKE", "PINK_CANDLE"); + safeAddMaterial(nonItem, "GRAY_CANDLE_CAKE", "GRAY_CANDLE"); + safeAddMaterial(nonItem, "LIGHT_GRAY_CANDLE_CAKE", "LIGHT_GRAY_CANDLE"); + safeAddMaterial(nonItem, "CYAN_CANDLE_CAKE", "CYAN_CANDLE"); + safeAddMaterial(nonItem, "PURPLE_CANDLE_CAKE", "PURPLE_CANDLE"); + safeAddMaterial(nonItem, "BLUE_CANDLE_CAKE", "BLUE_CANDLE"); + safeAddMaterial(nonItem, "BROWN_CANDLE_CAKE", "BROWN_CANDLE"); + safeAddMaterial(nonItem, "GREEN_CANDLE_CAKE", "GREEN_CANDLE"); + safeAddMaterial(nonItem, "RED_CANDLE_CAKE", "RED_CANDLE"); + safeAddMaterial(nonItem, "BLACK_CANDLE_CAKE", "BLACK_CANDLE"); + safeAddMaterial(nonItem, "POWDER_SNOW", "SNOWBALL"); + safeAddMaterial(nonItem, "CAVE_VINES", "GLOW_BERRIES"); + safeAddMaterial(nonItem, "CAVE_VINES_PLANT", "GLOW_BERRIES"); + safeAddMaterial(nonItem, "BIG_DRIPLEAF_STEM", "BIG_DRIPLEAF"); + safeAddMaterial(nonItem, "POTTED_AZALEA_BUSH", "FLOWER_POT"); + safeAddMaterial(nonItem, "POTTED_FLOWERING_AZALEA_BUSH", "FLOWER_POT"); + + safeAddEntity(entityToMaterial, "EXPERIENCE_ORB", "EXPERIENCE_BOTTLE"); + safeAddEntity(entityToMaterial, "AREA_EFFECT_CLOUD", "POTION"); + safeAddEntity(entityToMaterial, "ELDER_GUARDIAN", "ELDER_GUARDIAN_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "WITHER_SKELETON", "WITHER_SKELETON_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "STRAY", "STRAY_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "EGG", "EGG"); + safeAddEntity(entityToMaterial, "LEASH_HITCH", "LEAD"); + safeAddEntity(entityToMaterial, "PAINTING", "PAINTING"); + safeAddEntity(entityToMaterial, "ARROW", "ARROW"); + safeAddEntity(entityToMaterial, "SNOWBALL", "SNOWBALL"); + safeAddEntity(entityToMaterial, "FIREBALL", "GHAST_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "SMALL_FIREBALL", "FIRE_CHARGE"); + safeAddEntity(entityToMaterial, "ENDER_PEARL", "ENDER_PEARL"); + safeAddEntity(entityToMaterial, "ENDER_SIGNAL", "ENDER_EYE"); + safeAddEntity(entityToMaterial, "THROWN_EXP_BOTTLE", "EXPERIENCE_BOTTLE"); + safeAddEntity(entityToMaterial, "ITEM_FRAME", "ITEM_FRAME"); + safeAddEntity(entityToMaterial, "WITHER_SKULL", "WITHER_SKELETON_SKULL"); + safeAddEntity(entityToMaterial, "PRIMED_TNT", "TNT"); + safeAddEntity(entityToMaterial, "HUSK", "HUSK_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "SPECTRAL_ARROW", "SPECTRAL_ARROW"); + safeAddEntity(entityToMaterial, "SHULKER_BULLET", "SHULKER_SHELL"); + safeAddEntity(entityToMaterial, "DRAGON_FIREBALL", "DRAGON_BREATH"); + safeAddEntity(entityToMaterial, "ZOMBIE_VILLAGER", "ZOMBIE_VILLAGER_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "SKELETON_HORSE", "SKELETON_HORSE_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "ZOMBIE_HORSE", "ZOMBIE_HORSE_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "ARMOR_STAND", "ARMOR_STAND"); + safeAddEntity(entityToMaterial, "DONKEY", "DONKEY_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "MULE", "MULE_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "EVOKER_FANGS", "EVOKER_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "EVOKER", "EVOKER_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "VEX", "VEX_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "VINDICATOR", "VINDICATOR_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "ILLUSIONER", "COMMAND_BLOCK"); + safeAddEntity(entityToMaterial, "MINECART_COMMAND", "COMMAND_BLOCK_MINECART"); + safeAddEntity(entityToMaterial, "BOAT", "OAK_BOAT"); + safeAddEntity(entityToMaterial, "MINECART", "MINECART"); + safeAddEntity(entityToMaterial, "MINECART_CHEST", "CHEST_MINECART"); + safeAddEntity(entityToMaterial, "MINECART_FURNACE", "FURNACE_MINECART"); + safeAddEntity(entityToMaterial, "MINECART_TNT", "TNT_MINECART"); + safeAddEntity(entityToMaterial, "MINECART_HOPPER", "HOPPER_MINECART"); + safeAddEntity(entityToMaterial, "MINECART_MOB_SPAWNER", "SPAWNER"); + safeAddEntity(entityToMaterial, "CREEPER", "CREEPER_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "SKELETON", "SKELETON_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "SPIDER", "SPIDER_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "GIANT", "ZOMBIE_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "ZOMBIE", "ZOMBIE_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "SLIME", "SLIME_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "GHAST", "GHAST_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "ZOMBIFIED_PIGLIN", "ZOMBIFIED_PIGLIN_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "ENDERMAN", "ENDERMAN_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "CAVE_SPIDER", "CAVE_SPIDER_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "SILVERFISH", "SILVERFISH_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "BLAZE", "BLAZE_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "MAGMA_CUBE", "MAGMA_CUBE_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "ENDER_DRAGON", "COMMAND_BLOCK"); + safeAddEntity(entityToMaterial, "WITHER", "COMMAND_BLOCK"); + safeAddEntity(entityToMaterial, "BAT", "BAT_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "WITCH", "WITCH_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "ENDERMITE", "ENDERMITE_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "GUARDIAN", "GUARDIAN_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "SHULKER", "SHULKER_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "PIG", "PIG_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "SHEEP", "SHEEP_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "COW", "COW_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "CHICKEN", "CHICKEN_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "SQUID", "SQUID_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "WOLF", "WOLF_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "MUSHROOM_COW", "MOOSHROOM_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "SNOWMAN", "CARVED_PUMPKIN"); + safeAddEntity(entityToMaterial, "OCELOT", "OCELOT_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "IRON_GOLEM", "CARVED_PUMPKIN"); + safeAddEntity(entityToMaterial, "HORSE", "HORSE_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "RABBIT", "RABBIT_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "POLAR_BEAR", "POLAR_BEAR_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "LLAMA", "LLAMA_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "LLAMA_SPIT", "LLAMA_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "PARROT", "PARROT_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "VILLAGER", "VILLAGER_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "ENDER_CRYSTAL", "END_CRYSTAL"); + safeAddEntity(entityToMaterial, "TURTLE", "TURTLE_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "PHANTOM", "PHANTOM_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "TRIDENT", "TRIDENT"); + safeAddEntity(entityToMaterial, "COD", "COD_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "SALMON", "SALMON_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "PUFFERFISH", "PUFFERFISH_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "TROPICAL_FISH", "TROPICAL_FISH_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "DROWNED", "DROWNED_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "DOLPHIN", "DOLPHIN_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "CAT", "CAT_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "PANDA", "PANDA_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "PILLAGER", "PILLAGER_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "RAVAGER", "RAVAGER_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "TRADER_LLAMA", "TRADER_LLAMA_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "WANDERING_TRADER", "WANDERING_TRADER_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "FOX", "FOX_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "BEE", "BEE_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "HOGLIN", "HOGLIN_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "PIGLIN", "PIGLIN_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "STRIDER", "STRIDER_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "ZOGLIN", "ZOGLIN_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "PIGLIN_BRUTE", "PIGLIN_BRUTE_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "AXOLOTL", "AXOLOTL_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "GLOW_ITEM_FRAME", "GLOW_ITEM_FRAME"); + safeAddEntity(entityToMaterial, "GLOW_SQUID", "GLOW_SQUID_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "GOAT", "GOAT_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "MARKER", "MAP"); + safeAddEntity(entityToMaterial, "ALLAY", "ALLAY_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "CHEST_BOAT", "OAK_CHEST_BOAT"); + safeAddEntity(entityToMaterial, "FROG", "FROG_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "TADPOLE", "TADPOLE_SPAWN_EGG"); + safeAddEntity(entityToMaterial, "WARDEN", "WARDEN_SPAWN_EGG"); + } + + private static boolean isValidMaterial(String material) { + return Material.getMaterial(material) != null; + } + + private static boolean isValidEntityType(String entityType) { + try { + EntityType.valueOf(entityType); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + + private static void safeAddMaterial(Map map, String key, String value) { + if (isValidMaterial(key) && isValidMaterial(value)) { + map.put(Material.valueOf(key), Material.valueOf(value)); + } + } + + private static void safeAddEntity(Map map, String key, String value) { + if (isValidEntityType(key) && isValidMaterial(value)) { + map.put(EntityType.valueOf(key), Material.valueOf(value)); + } + } + + /** + * Fills a map with materials that are not items but an item material + * was manually provided for their item representation. + * + * @param map the map to fill + */ + public static void fillNonItem(Map map) { + map.putAll(nonItem); } - public static void fillEntityTypeMaterial(HashMap map) { - map.put(EntityType.EXPERIENCE_ORB, Material.EXPERIENCE_BOTTLE); - map.put(EntityType.AREA_EFFECT_CLOUD, Material.POTION); - map.put(EntityType.ELDER_GUARDIAN, Material.ELDER_GUARDIAN_SPAWN_EGG); - map.put(EntityType.WITHER_SKELETON, Material.WITHER_SKELETON_SPAWN_EGG); - map.put(EntityType.STRAY, Material.STRAY_SPAWN_EGG); - map.put(EntityType.EGG, Material.EGG); - map.put(EntityType.LEASH_HITCH, Material.LEAD); - map.put(EntityType.PAINTING, Material.PAINTING); - map.put(EntityType.ARROW, Material.ARROW); - map.put(EntityType.SNOWBALL, Material.SNOWBALL); - map.put(EntityType.FIREBALL, Material.GHAST_SPAWN_EGG); - map.put(EntityType.SMALL_FIREBALL, Material.FIRE_CHARGE); - map.put(EntityType.ENDER_PEARL, Material.ENDER_PEARL); - map.put(EntityType.ENDER_SIGNAL, Material.ENDER_EYE); - map.put(EntityType.THROWN_EXP_BOTTLE, Material.EXPERIENCE_BOTTLE); - map.put(EntityType.ITEM_FRAME, Material.ITEM_FRAME); - map.put(EntityType.WITHER_SKULL, Material.WITHER_SKELETON_SKULL); - map.put(EntityType.PRIMED_TNT, Material.TNT); - map.put(EntityType.HUSK, Material.HUSK_SPAWN_EGG); - map.put(EntityType.SPECTRAL_ARROW, Material.SPECTRAL_ARROW); - map.put(EntityType.SHULKER_BULLET, Material.SHULKER_SHELL); - map.put(EntityType.DRAGON_FIREBALL, Material.DRAGON_BREATH); - map.put(EntityType.ZOMBIE_VILLAGER, Material.ZOMBIE_VILLAGER_SPAWN_EGG); - map.put(EntityType.SKELETON_HORSE, Material.SKELETON_HORSE_SPAWN_EGG); - map.put(EntityType.ZOMBIE_HORSE, Material.ZOMBIE_HORSE_SPAWN_EGG); - map.put(EntityType.ARMOR_STAND, Material.ARMOR_STAND); - map.put(EntityType.DONKEY, Material.DONKEY_SPAWN_EGG); - map.put(EntityType.MULE, Material.MULE_SPAWN_EGG); - map.put(EntityType.EVOKER_FANGS, Material.EVOKER_SPAWN_EGG); - map.put(EntityType.EVOKER, Material.EVOKER_SPAWN_EGG); - map.put(EntityType.VEX, Material.VEX_SPAWN_EGG); - map.put(EntityType.VINDICATOR, Material.VINDICATOR_SPAWN_EGG); - map.put(EntityType.ILLUSIONER, Material.COMMAND_BLOCK); - map.put(EntityType.MINECART_COMMAND, Material.COMMAND_BLOCK_MINECART); - map.put(EntityType.BOAT, Material.OAK_BOAT); - map.put(EntityType.MINECART, Material.MINECART); - map.put(EntityType.MINECART_CHEST, Material.CHEST_MINECART); - map.put(EntityType.MINECART_FURNACE, Material.FURNACE_MINECART); - map.put(EntityType.MINECART_TNT, Material.TNT_MINECART); - map.put(EntityType.MINECART_HOPPER, Material.HOPPER_MINECART); - map.put(EntityType.MINECART_MOB_SPAWNER, Material.SPAWNER); - map.put(EntityType.CREEPER, Material.CREEPER_SPAWN_EGG); - map.put(EntityType.SKELETON, Material.SKELETON_SPAWN_EGG); - map.put(EntityType.SPIDER, Material.SPIDER_SPAWN_EGG); - map.put(EntityType.GIANT, Material.ZOMBIE_SPAWN_EGG); - map.put(EntityType.ZOMBIE, Material.ZOMBIE_SPAWN_EGG); - map.put(EntityType.SLIME, Material.SLIME_SPAWN_EGG); - map.put(EntityType.GHAST, Material.GHAST_SPAWN_EGG); - map.put(EntityType.ZOMBIFIED_PIGLIN, Material.ZOMBIFIED_PIGLIN_SPAWN_EGG); - map.put(EntityType.ENDERMAN, Material.ENDERMAN_SPAWN_EGG); - map.put(EntityType.CAVE_SPIDER, Material.CAVE_SPIDER_SPAWN_EGG); - map.put(EntityType.SILVERFISH, Material.SILVERFISH_SPAWN_EGG); - map.put(EntityType.BLAZE, Material.BLAZE_SPAWN_EGG); - map.put(EntityType.MAGMA_CUBE, Material.MAGMA_CUBE_SPAWN_EGG); - map.put(EntityType.ENDER_DRAGON, Material.COMMAND_BLOCK); - map.put(EntityType.WITHER, Material.COMMAND_BLOCK); - map.put(EntityType.BAT, Material.BAT_SPAWN_EGG); - map.put(EntityType.WITCH, Material.WITCH_SPAWN_EGG); - map.put(EntityType.ENDERMITE, Material.ENDERMITE_SPAWN_EGG); - map.put(EntityType.GUARDIAN, Material.GUARDIAN_SPAWN_EGG); - map.put(EntityType.SHULKER, Material.SHULKER_SPAWN_EGG); - map.put(EntityType.PIG, Material.PIG_SPAWN_EGG); - map.put(EntityType.SHEEP, Material.SHEEP_SPAWN_EGG); - map.put(EntityType.COW, Material.COW_SPAWN_EGG); - map.put(EntityType.CHICKEN, Material.CHICKEN_SPAWN_EGG); - map.put(EntityType.SQUID, Material.SQUID_SPAWN_EGG); - map.put(EntityType.WOLF, Material.WOLF_SPAWN_EGG); - map.put(EntityType.MUSHROOM_COW, Material.MOOSHROOM_SPAWN_EGG); - map.put(EntityType.SNOWMAN, Material.CARVED_PUMPKIN); - map.put(EntityType.OCELOT, Material.OCELOT_SPAWN_EGG); - map.put(EntityType.IRON_GOLEM, Material.CARVED_PUMPKIN); - map.put(EntityType.HORSE, Material.HORSE_SPAWN_EGG); - map.put(EntityType.RABBIT, Material.RABBIT_SPAWN_EGG); - map.put(EntityType.POLAR_BEAR, Material.POLAR_BEAR_SPAWN_EGG); - map.put(EntityType.LLAMA, Material.LLAMA_SPAWN_EGG); - map.put(EntityType.LLAMA_SPIT, Material.LLAMA_SPAWN_EGG); - map.put(EntityType.PARROT, Material.PARROT_SPAWN_EGG); - map.put(EntityType.VILLAGER, Material.VILLAGER_SPAWN_EGG); - map.put(EntityType.ENDER_CRYSTAL, Material.END_CRYSTAL); - map.put(EntityType.TURTLE, Material.TURTLE_SPAWN_EGG); - map.put(EntityType.PHANTOM, Material.PHANTOM_SPAWN_EGG); - map.put(EntityType.TRIDENT, Material.TRIDENT); - map.put(EntityType.COD, Material.COD_SPAWN_EGG); - map.put(EntityType.SALMON, Material.SALMON_SPAWN_EGG); - map.put(EntityType.PUFFERFISH, Material.PUFFERFISH_SPAWN_EGG); - map.put(EntityType.TROPICAL_FISH, Material.TROPICAL_FISH_SPAWN_EGG); - map.put(EntityType.DROWNED, Material.DROWNED_SPAWN_EGG); - map.put(EntityType.DOLPHIN, Material.DOLPHIN_SPAWN_EGG); - map.put(EntityType.CAT, Material.CAT_SPAWN_EGG); - map.put(EntityType.PANDA, Material.PANDA_SPAWN_EGG); - map.put(EntityType.PILLAGER, Material.PILLAGER_SPAWN_EGG); - map.put(EntityType.RAVAGER, Material.RAVAGER_SPAWN_EGG); - map.put(EntityType.TRADER_LLAMA, Material.TRADER_LLAMA_SPAWN_EGG); - map.put(EntityType.WANDERING_TRADER, Material.WANDERING_TRADER_SPAWN_EGG); - map.put(EntityType.FOX, Material.FOX_SPAWN_EGG); - map.put(EntityType.BEE, Material.BEE_SPAWN_EGG); - map.put(EntityType.HOGLIN, Material.HOGLIN_SPAWN_EGG); - map.put(EntityType.PIGLIN, Material.PIGLIN_SPAWN_EGG); - map.put(EntityType.STRIDER, Material.STRIDER_SPAWN_EGG); - map.put(EntityType.ZOGLIN, Material.ZOGLIN_SPAWN_EGG); - map.put(EntityType.PIGLIN_BRUTE, Material.PIGLIN_BRUTE_SPAWN_EGG); - map.put(EntityType.AXOLOTL, Material.AXOLOTL_SPAWN_EGG); - map.put(EntityType.GLOW_ITEM_FRAME, Material.GLOW_ITEM_FRAME); - map.put(EntityType.GLOW_SQUID, Material.GLOW_SQUID_SPAWN_EGG); - map.put(EntityType.GOAT, Material.GOAT_SPAWN_EGG); - map.put(EntityType.MARKER, Material.MAP); - map.put(EntityType.ALLAY, Material.ALLAY_SPAWN_EGG); - map.put(EntityType.CHEST_BOAT, Material.OAK_CHEST_BOAT); - map.put(EntityType.FROG, Material.FROG_SPAWN_EGG); - map.put(EntityType.TADPOLE, Material.TADPOLE_SPAWN_EGG); - map.put(EntityType.WARDEN, Material.WARDEN_SPAWN_EGG); + /** + * Fills a map with EntityTypes but an item material + * was manually provided for their item representation. + * + * @param map the map to fill + */ + public static void fillEntityTypeMaterial(Map map) { + map.putAll(entityToMaterial); } } diff --git a/src/main/java/us/mytheria/bloblib/utilities/PlayerUtil.java b/src/main/java/us/mytheria/bloblib/utilities/PlayerUtil.java index 1b85a2c0..ac897c66 100644 --- a/src/main/java/us/mytheria/bloblib/utilities/PlayerUtil.java +++ b/src/main/java/us/mytheria/bloblib/utilities/PlayerUtil.java @@ -77,7 +77,7 @@ public static Block getTargetBlock(Player player) { */ public static BlockFace getBlockFace(Player player) { List lastTwoTargetBlocks = player.getLastTwoTargetBlocks(null, 100); - if (lastTwoTargetBlocks.size() != 2 || !lastTwoTargetBlocks.get(1).getType().isOccluding()) return null; + if (lastTwoTargetBlocks.size() != 2) return null; Block targetBlock = lastTwoTargetBlocks.get(1); Block adjacentBlock = lastTwoTargetBlocks.get(0); return targetBlock.getFace(adjacentBlock); @@ -89,9 +89,9 @@ public static BlockFace getBlockFace(Player player) { * @param player the player's who's targeted block is to be checked. * @return the adjacent block, or null if the targeted block is non-occluding. */ - public static Block getAdyacentBlock(Player player) { + public static Block getAdjacentBlock(Player player) { List lastTwoTargetBlocks = player.getLastTwoTargetBlocks(null, 100); - if (lastTwoTargetBlocks.size() != 2 || !lastTwoTargetBlocks.get(1).getType().isOccluding()) return null; + if (lastTwoTargetBlocks.size() != 2 || !lastTwoTargetBlocks.get(1).getType().isSolid()) return null; return lastTwoTargetBlocks.get(0); } diff --git a/src/main/java/us/mytheria/bloblib/utilities/PotionEffectLib.java b/src/main/java/us/mytheria/bloblib/utilities/PotionEffectLib.java new file mode 100644 index 00000000..d76f6b95 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/utilities/PotionEffectLib.java @@ -0,0 +1,91 @@ +package us.mytheria.bloblib.utilities; + +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +public class PotionEffectLib { + + public static void serialize(ConfigurationSection section, PotionEffect effect) { + section.set("Type", effect.getType().getName()); + section.set("Amplifier", effect.getAmplifier()); + section.set("Duration", effect.getDuration()); + section.set("Ambient", effect.isAmbient()); + section.set("Particles", effect.hasParticles()); + section.set("Icon", effect.hasIcon()); + } + + public static PotionEffect deserialize(ConfigurationSection section) { + if (!section.isString("Type")) + return null; + PotionEffectType type; + try { + type = PotionEffectType.getByName(section.getString("Type")); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid potion effect type in '" + section.getName() + "': " + section.getString("Type")); + } + if (!section.isInt("Amplifier")) + throw new IllegalArgumentException("Invalid amplifier in '" + section.getName() + "': " + section.getString("Amplifier")); + if (!section.isInt("Duration")) + throw new IllegalArgumentException("Invalid duration in '" + section.getName() + "': " + section.getString("Duration")); + if (!section.isBoolean("Ambient")) + throw new IllegalArgumentException("Invalid ambient in '" + section.getName() + "': " + section.getString("Ambient")); + if (!section.isBoolean("Particles")) + throw new IllegalArgumentException("Invalid particles in '" + section.getName() + "': " + section.getString("Particles")); + if (!section.isBoolean("Icon")) + throw new IllegalArgumentException("Invalid icon in '" + section.getName() + "': " + section.getString("Icon")); + int amplifier = section.getInt("Amplifier"); + int duration = section.getInt("Duration"); + boolean ambient = section.getBoolean("Ambient"); + boolean particles = section.getBoolean("Particles"); + boolean icon = section.getBoolean("Icon"); + return new PotionEffect(type, duration, amplifier, ambient, particles, icon); + } + + public static String stringSerialize(PotionEffect effect) { + return effect.getType().getName() + " " + effect.getAmplifier() + " " + effect.getDuration() + " " + effect.isAmbient() + " " + effect.hasParticles() + " " + effect.hasIcon(); + } + + public static PotionEffect deserializeString(String string) { + String[] split = string.split(" "); + if (split.length != 6) + throw new IllegalArgumentException("Invalid potion effect string: " + string); + PotionEffectType type; + try { + type = PotionEffectType.getByName(split[0]); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid potion effect type in '" + string + "': " + split[0]); + } + int amplifier; + try { + amplifier = Integer.parseInt(split[1]); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid amplifier in '" + string + "': " + split[1]); + } + int duration; + try { + duration = Integer.parseInt(split[2]); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid duration in '" + string + "': " + split[2]); + } + boolean ambient; + try { + ambient = Boolean.parseBoolean(split[3]); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid ambient in '" + string + "': " + split[3]); + } + boolean particles; + try { + particles = Boolean.parseBoolean(split[4]); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid particles in '" + string + "': " + split[4]); + } + boolean icon; + try { + icon = Boolean.parseBoolean(split[5]); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid icon in '" + string + "': " + split[5]); + } + return new PotionEffect(type, duration, amplifier, ambient, particles, icon); + } +} diff --git a/src/main/java/us/mytheria/bloblib/utilities/ProjectileUtil.java b/src/main/java/us/mytheria/bloblib/utilities/ProjectileUtil.java new file mode 100644 index 00000000..f1f6ba4f --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/utilities/ProjectileUtil.java @@ -0,0 +1,40 @@ +package us.mytheria.bloblib.utilities; + +import org.bukkit.entity.EntityType; + +import java.util.HashSet; +import java.util.Set; + +public class ProjectileUtil { + private static final Set projectiles; + + static { + projectiles = new HashSet<>(); + projectiles.add("ARROW"); + projectiles.add("DRAGON_FIREBALL"); + projectiles.add("EGG"); + projectiles.add("ENDER_PEARL"); + projectiles.add("FIREBALL"); + projectiles.add("FIREWORK"); + projectiles.add("FISHING_HOOK"); + projectiles.add("SPLASH_POTION"); + projectiles.add("LLAMA_SPIT"); + projectiles.add("SHULKER_BULLET"); + projectiles.add("SNOWBALL"); + projectiles.add("SPECTRAL_ARROW"); + projectiles.add("THROWN_EXP_BOTTLE"); + projectiles.add("TRIDENT"); + projectiles.add("WITHER_SKULL"); + } + + /** + * Returns whether the given entity type is a projectile. + * + * @param entityType the entity type to check + * @return whether the given entity type is a projectile + */ + public static boolean isProjectile(EntityType entityType) { + String name = entityType.name(); + return projectiles.contains(name); + } +} diff --git a/src/main/java/us/mytheria/bloblib/utilities/ResourceUtil.java b/src/main/java/us/mytheria/bloblib/utilities/ResourceUtil.java index 330731e3..f9e6de18 100644 --- a/src/main/java/us/mytheria/bloblib/utilities/ResourceUtil.java +++ b/src/main/java/us/mytheria/bloblib/utilities/ResourceUtil.java @@ -37,11 +37,11 @@ public static void moveResource(File file, InputStream inputStream) { * but are so in the temp file. * Comments will be overwritten by the new updated file. * - * @param newFile the file to write to + * @param existingFile the file to write to * @param updateYamlConfiguration the file to read from */ - public static void writeNewValues(File newFile, YamlConfiguration updateYamlConfiguration) { - FileConfiguration existingYamlConfig = YamlConfiguration.loadConfiguration(newFile); + public static void writeNewValues(File existingFile, YamlConfiguration updateYamlConfiguration) { + FileConfiguration existingYamlConfig = YamlConfiguration.loadConfiguration(existingFile); Set keys = updateYamlConfiguration.getConfigurationSection("").getKeys(true); keys.forEach(key -> { if (!updateYamlConfiguration.isConfigurationSection(key)) { @@ -52,12 +52,13 @@ public static void writeNewValues(File newFile, YamlConfiguration updateYamlConf if (inLine.size() > 0) existingYamlConfig.setInlineComments(key, inLine); // if it's not a section, it's a value + if (existingYamlConfig.contains(key)) return; } if (existingYamlConfig.isConfigurationSection(key)) return; //if it exists, skip existingYamlConfig.set(key, updateYamlConfiguration.get(key)); // write }); try { - updateYamlConfiguration.save(newFile); + existingYamlConfig.save(existingFile); } catch (IOException e) { e.printStackTrace(); } diff --git a/src/main/java/us/mytheria/bloblib/utilities/SerializationLib.java b/src/main/java/us/mytheria/bloblib/utilities/SerializationLib.java index 09ecb43e..bfefb709 100644 --- a/src/main/java/us/mytheria/bloblib/utilities/SerializationLib.java +++ b/src/main/java/us/mytheria/bloblib/utilities/SerializationLib.java @@ -4,15 +4,50 @@ import org.bukkit.block.Block; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.EntityType; +import org.bukkit.potion.PotionEffect; +import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.util.BlockVector; import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; import us.mytheria.bloblib.BlobLib; import java.math.BigDecimal; import java.math.BigInteger; import java.util.UUID; +import java.util.concurrent.*; public class SerializationLib { + private static SerializationLib instance; + private final ExecutorService service; + + private SerializationLib() { + this.service = Executors.newCachedThreadPool(); + } + + public static SerializationLib getInstance(BlobLib plugin) { + if (instance == null) { + if (plugin == null) + throw new NullPointerException("injected dependency is null"); + SerializationLib.instance = new SerializationLib(); + } + return instance; + } + + public void shutdown() { + service.shutdown(); + } + + public static SerializationLib getInstance() { + return getInstance(null); + } + + public static String serialize(PotionEffect effect) { + return PotionEffectLib.stringSerialize(effect); + } + + public static PotionEffect deserializePotionEffect(String string) { + return PotionEffectLib.deserializeString(string); + } public static String serialize(Color color) { return color.getRed() + "," + color.getGreen() + "," + color.getBlue(); @@ -64,7 +99,12 @@ public static String serialize(Location location) { public static Location deserializeLocation(String string) { String[] split = string.split(","); - return new Location(Bukkit.getWorld(split[0]), Double.parseDouble(split[1]), Double.parseDouble(split[2]), Double.parseDouble(split[3]), Float.parseFloat(split[4]), Float.parseFloat(split[5])); + if (split.length == 4) + return new Location(Bukkit.getWorld(split[0]), Double.parseDouble(split[1]), Double.parseDouble(split[2]), Double.parseDouble(split[3])); + else if (split.length == 6) + return new Location(Bukkit.getWorld(split[0]), Double.parseDouble(split[1]), Double.parseDouble(split[2]), Double.parseDouble(split[3]), Float.parseFloat(split[4]), Float.parseFloat(split[5])); + else + throw new IllegalArgumentException("Invalid location string: " + string); } public static String serialize(Block block) { @@ -96,8 +136,54 @@ public static String serialize(World world) { return world.getName(); } + /** + * Will attempt to deserialize a world for + * a minute. If fails, will throw a RuntimeException. + * + * @param string World name + * @param period period to check for world + * @return World + */ + @NotNull + public static World deserializeWorld(String string, int period) { + Future future = getInstance().service.submit(() -> { + CompletableFuture completableFuture = new CompletableFuture<>(); + new BukkitRunnable() { + @Override + public void run() { + World world = Bukkit.getWorld(string); + if (completableFuture.isDone() || completableFuture.isCancelled() || completableFuture.isCompletedExceptionally()) + cancel(); + if (world == null) + return; + completableFuture.complete(world); + cancel(); + } + }.runTaskTimerAsynchronously(BlobLib.getInstance(), 0, period); + World world; + try { + world = completableFuture.get(1, TimeUnit.MINUTES); + } catch (Exception e) { + world = null; + } + return world; + }); + try { + return future.get(); + } catch (Exception e) { + throw new RuntimeException("World " + string + " not found after waiting 60 seconds!"); + } + } + + /** + * Will attempt to deserialize a world for + * a minute. If fails, will throw a RuntimeException. + * + * @param string World name + * @return the World + */ public static World deserializeWorld(String string) { - return Bukkit.getWorld(string); + return deserializeWorld(string, 15); } public static String serialize(Material material) { diff --git a/src/main/java/us/mytheria/bloblib/utilities/Structrador.java b/src/main/java/us/mytheria/bloblib/utilities/Structrador.java new file mode 100644 index 00000000..f5233787 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/utilities/Structrador.java @@ -0,0 +1,343 @@ +package us.mytheria.bloblib.utilities; + +import me.anjoismysign.anjo.entities.Uber; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockState; +import org.bukkit.block.structure.Mirror; +import org.bukkit.block.structure.StructureRotation; +import org.bukkit.craftbukkit.v1_20_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Hanging; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.structure.Palette; +import org.bukkit.structure.Structure; +import org.bukkit.util.BlockVector; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; +import us.mytheria.bloblib.bukkit.BlobBlockState; + +import java.io.*; +import java.sql.Blob; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Random; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; + +/** + * Your decorator guy for structure related stuff. + * Includes I/O, fill and placement. + * Needs further testing + */ +public class Structrador { + private final Structure structure; + private final JavaPlugin plugin; + + /** + * Will create a structure from the given location and radius. + * Let's say that the center is at 0,0,0 and the radius is 5,5,5. + * The structure will cover from -5,-5,-5 to 5,5,5. + * + * @param plugin - The plugin that will be used to create the structure. + * @param center - The center of the structure. + * @param radius - The radius of the structure. + * @param includeEntities - If the entities present in the structure should be included. + * @return A Structrador with the structure. + */ + @NotNull + public static Structrador CENTERED(JavaPlugin plugin, Location center, + Vector radius, boolean includeEntities) { + Structure structure = Bukkit.getStructureManager().createStructure(); + structure.fill(Objects.requireNonNull(center).clone().subtract(Objects.requireNonNull(radius)), + center.clone().add(radius), includeEntities); + return new Structrador(structure, plugin); + } + + /** + * Will create a structure from the given locations. + * The structure will cover from the first corner to the second corner. + * + * @param plugin - The plugin that will be used to create the structure. + * @param pos1 - The first corner of the structure. + * @param pos2 - The second corner of the structure. + * @param includeEntities - If the entities present in the structure should be included. + * @return A Structrador with the structure. + */ + @NotNull + public static Structrador CORNERS(JavaPlugin plugin, Location pos1, + Location pos2, boolean includeEntities) { + Structure structure = Bukkit.getStructureManager().createStructure(); + if (!Objects.equals(Objects.requireNonNull(pos1).getWorld(), Objects.requireNonNull(pos2).getWorld())) + throw new IllegalArgumentException("Locations must be in the same world."); + Location min = new Location(pos1.getWorld(), Math.min(pos1.getX(), pos2.getX()), + Math.min(pos1.getY(), pos2.getY()), Math.min(pos1.getZ(), pos2.getZ())); + Location max = new Location(pos1.getWorld(), Math.max(pos1.getX(), pos2.getX()), + Math.max(pos1.getY(), pos2.getY()), Math.max(pos1.getZ(), pos2.getZ())); + structure.fill(min, max, includeEntities); + return new Structrador(structure, plugin); + } + + public Structrador(Structure structure, JavaPlugin plugin) { + this.structure = Objects.requireNonNull(structure); + this.plugin = Objects.requireNonNull(plugin); + } + + public Structrador(File file, JavaPlugin plugin) { + Structure structure; + try { + structure = Bukkit.getStructureManager().loadStructure(file); + } catch (IOException e) { + throw new RuntimeException(e); + } + this.structure = structure; + this.plugin = Objects.requireNonNull(plugin); + } + + public Structrador(InputStream inputStream, JavaPlugin plugin) { + Structure structure; + try { + structure = Bukkit.getStructureManager().loadStructure(inputStream); + } catch (IOException e) { + throw new RuntimeException(e); + } + this.structure = structure; + this.plugin = Objects.requireNonNull(plugin); + } + + public Structrador(byte[] bytes, JavaPlugin plugin) { + this(new ByteArrayInputStream(bytes), plugin); + } + + public Structrador(Blob blob, JavaPlugin plugin) { + this(BlobUtil.blobToInputStream(blob), plugin); + } + + public Structure getStructure() { + return structure; + } + + public ByteArrayOutputStream toOutputStream() { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try { + Bukkit.getStructureManager().saveStructure(outputStream, structure); + } catch (IOException e) { + throw new RuntimeException(e); + } + return outputStream; + } + + public byte[] toByteArray() { + return toOutputStream().toByteArray(); + } + + public Blob toBlob() { + return BlobUtil.byteArrayOutputStreamToBlob(toOutputStream()); + } + + /** + * Will place the structure at the given location in the same tick. + * + * @param location - The location to place the structure at. + * @param includeEntities - If the entities present in the structure should be spawned. + * @param structureRotation - The rotation of the structure. + * @param mirror - The mirror settings of the structure. + * @param palette - The palette index of the structure to use, starting at 0, or -1 to pick a random palette. + * @param integrity - Determines how damaged the building should look by randomly skipping blocks to place. This value can range from 0 to 1. With 0 removing all blocks and 1 spawning the structure in pristine condition. + * @param random - The randomizer used for setting the structure's LootTables and integrity. + */ + public void simultaneousPlace(Location location, boolean includeEntities, + StructureRotation structureRotation, Mirror mirror, int palette, + float integrity, Random random) { + structure.place(location, includeEntities, structureRotation, mirror, palette, integrity, random); + } + + /** + * Will place the structure at the given location in multiple ticks. + * The bigger the structure, the longer it will take to place. + * TODO: mirror, palette, integrity and random are not used. + * + * @param location - The location to place the structure at. + * @param includeEntities - If the entities present in the structure should be spawned. + * @param structureRotation - The rotation of the structure. + * @param mirror - The mirror settings of the structure. + * @param palette - The palette index of the structure to use, starting at 0, or -1 to pick a random palette. + * @param integrity - Determines how damaged the building should look by randomly skipping blocks to place. This value can range from 0 to 1. With 0 removing all blocks and 1 spawning the structure in pristine condition. + * @param random - The randomizer used for setting the structure's LootTables and integrity. + * @param maxPlacedPerPeriod - The maximum amount of blocks/entities placed per period. + * @param period - The period between each placement in ticks. + * @param placedBlockConsumer - The consumer that will be called when a block is placed. + * @param placedEntityConsumer - The consumer that will be called when an entity is placed. + * @return A CompletableFuture that completes when the structure is placed. + */ + @NotNull + public CompletableFuture chainedPlace(Location location, boolean includeEntities, + StructureRotation structureRotation, Mirror mirror, + int palette, float integrity, Random random, + int maxPlacedPerPeriod, long period, + Consumer placedBlockConsumer, + Consumer placedEntityConsumer) { + /* + * The location of the returned block states and entities + * are offsets relative to the structure's position that + * is provided once the structure is placed into the world. + */ + CompletableFuture chainedPlace = new CompletableFuture<>(); + List palettes = structure.getPalettes(); + List blocks = palettes.stream().map(Palette::getBlocks) + .flatMap(List::stream).toList(); + Iterator iterator = blocks.iterator(); + World world = location.getWorld(); + if (world == null) + throw new IllegalArgumentException("Location must have a world."); + var nmsWorld = ((CraftWorld) world).getHandle(); + int degree; + Vector blockOffset; + Vector entityOffset; + BlockVector size = structure.getSize(); + float extraYaw; + switch (structureRotation) { + case NONE -> { + degree = 0; + blockOffset = new Vector(0, 0, 0); + entityOffset = new Vector(0, 0, 0); + extraYaw = 0; + } + case CLOCKWISE_90 -> { + degree = 270; + blockOffset = new Vector(size.getX() - 1, 0, 0); + entityOffset = new Vector(size.getX(), 0, 0); + extraYaw = 90; + } + case CLOCKWISE_180 -> { + degree = 180; + blockOffset = new Vector(size.getX() - 1, 0, size.getZ() - 1); + entityOffset = new Vector(size.getX(), 0, size.getZ()); + extraYaw = 180; + } + case COUNTERCLOCKWISE_90 -> { + degree = 90; + blockOffset = new Vector(0, 0, size.getZ() - 1); + entityOffset = new Vector(0, 0, size.getZ()); + extraYaw = 270; + } + default -> { + throw new IllegalStateException("Unexpected value: " + structureRotation); + } + } + try { + // Will place blocks + CompletableFuture paletteFuture = new CompletableFuture<>(); + BukkitRunnable placeTask = new BukkitRunnable() { + @Override + public void run() { + Uber placed = Uber.drive(0); + while (iterator.hasNext() && placed.thanks() < maxPlacedPerPeriod) { + BlockState next = iterator.next(); + Vector nextVector = next.getLocation().toVector(); + Vector result = VectorUtil.rotateVector(nextVector, degree); + Location blockLocation = location.clone() + .add(result.getX() + blockOffset.getX(), + result.getY() + blockOffset.getY(), + result.getZ() + blockOffset.getZ()); + BlobBlockState state = BlobBlockState.of(next); + state.update(true, false, blockLocation); + BlockState current = blockLocation.getBlock().getState(); + placedBlockConsumer.accept(current); + placed.talk(placed.thanks() + 1); + } + if (!iterator.hasNext()) { + paletteFuture.complete(null); + this.cancel(); + } + } + }; + placeTask.runTaskTimer(plugin, 1L, period); + //Once blocks are placed, will place entities + paletteFuture.whenComplete((aVoid, throwable) -> { + if (!includeEntities) { + chainedPlace.complete(null); + return; + } + Iterator entityIterator = structure.getEntities().iterator(); + BukkitRunnable entityTask = new BukkitRunnable() { + @Override + public void run() { + Uber maxPlaced = Uber.drive(0); + while (entityIterator.hasNext() && maxPlaced.thanks() < maxPlacedPerPeriod) { + Entity next = entityIterator.next(); + Location nextLocation = next.getLocation(); + Vector nextVector = nextLocation.toVector(); + Vector result = VectorUtil.floatRotateVector(nextVector, degree); + Location entityLocation = location.clone() + .add(result.getX() + entityOffset.getX(), + result.getY() + entityOffset.getY(), + result.getZ() + entityOffset.getZ()); + entityLocation.setYaw(nextLocation.getYaw() + extraYaw); + boolean isSilent = next.isSilent(); + next.setSilent(true); + /* + * Intermediate level of API-NMS + * Known to work in 1.20.1 + * Might break in future versions + */ + var nmsEntity = ((CraftEntity) next).getHandle(); + if (!nmsWorld.tryAddFreshEntityWithPassengers + (nmsEntity, CreatureSpawnEvent.SpawnReason.CUSTOM)) + continue; + next = nmsEntity.getBukkitEntity(); + next.teleport(entityLocation); + if (next instanceof Hanging hanging) { + BlockFace facing = hanging.getFacing(); + hanging.setFacingDirection( + BlockFaceUtil.rotate(facing, structureRotation), + true); + } + next.setSilent(isSilent); + placedEntityConsumer.accept(next); + maxPlaced.talk(maxPlaced.thanks() + 1); + } + if (!iterator.hasNext()) { + chainedPlace.complete(null); + this.cancel(); + } + } + }; + entityTask.runTaskTimer(plugin, 1L, period); + }); + } catch (Exception e) { + chainedPlace.completeExceptionally(e); + } + return chainedPlace; + } + + /** + * Will place the structure at the given location in multiple ticks. + * The bigger the structure, the longer it will take to place. + * Places 1 block/entity per tick. + * TODO: structureRotation, mirror, palette, integrity and random are not used. + * + * @param location - The location to place the structure at. + * @param includeEntities - If the entities present in the structure should be spawned. + * @param structureRotation - The rotation of the structure. + * @param mirror - The mirror settings of the structure. + * @param palette - The palette index of the structure to use, starting at 0, or -1 to pick a random palette. + * @param integrity - Determines how damaged the building should look by randomly skipping blocks to place. This value can range from 0 to 1. With 0 removing all blocks and 1 spawning the structure in pristine condition. + * @param random - The randomizer used for setting the structure's LootTables and integrity. + * @return A CompletableFuture that completes when the structure is placed. + */ + public CompletableFuture chainedPlace(Location location, boolean includeEntities, + StructureRotation structureRotation, Mirror mirror, + int palette, float integrity, Random random) { + return chainedPlace(location, includeEntities, structureRotation, mirror, palette, + integrity, random, 1, 1, blockState -> { + }, entity -> { + }); + } +} \ No newline at end of file diff --git a/src/main/java/us/mytheria/bloblib/utilities/TextColor.java b/src/main/java/us/mytheria/bloblib/utilities/TextColor.java index 1ed6838f..94fa5c7b 100644 --- a/src/main/java/us/mytheria/bloblib/utilities/TextColor.java +++ b/src/main/java/us/mytheria/bloblib/utilities/TextColor.java @@ -6,9 +6,9 @@ import java.util.regex.Pattern; public class TextColor { + private static final Pattern hexPattern = Pattern.compile("&#([A-Fa-f0-9]{6})"); public String translateHexColorCodes(char alternateColorCode, String message) { - final Pattern hexPattern = Pattern.compile("&#([A-Fa-f0-9]{6})"); Matcher matcher = hexPattern.matcher(message); StringBuffer buffer = new StringBuffer(message.length() + 4 * 8); while (matcher.find()) { diff --git a/src/main/java/us/mytheria/bloblib/utilities/VectorUtil.java b/src/main/java/us/mytheria/bloblib/utilities/VectorUtil.java index 95814055..05e4bbdf 100644 --- a/src/main/java/us/mytheria/bloblib/utilities/VectorUtil.java +++ b/src/main/java/us/mytheria/bloblib/utilities/VectorUtil.java @@ -1,8 +1,8 @@ package us.mytheria.bloblib.utilities; -import org.bukkit.Bukkit; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; public class VectorUtil { @@ -30,6 +30,14 @@ public static void absoluteVector(Vector vector) { vector.setZ(Math.abs(vector.getZ())); } + /** + * Rotates a vector. + * + * @param vector the vector to rotate + * @param degree the degree to rotate + * @return the rotated vector + */ + @NotNull public static Vector rotateVector(Vector vector, int degree) { switch (degree) { case 270 -> { @@ -41,9 +49,40 @@ public static Vector rotateVector(Vector vector, int degree) { case 90 -> { return new Vector(vector.getBlockZ(), vector.getBlockY(), IntegerUtil.invertSign(vector.getBlockX())); } + case 0 -> { + return vector; + } + default -> { + throw new IllegalArgumentException("Degree must be 0, 90, 180, or 270, but was " + degree); + } + } + } + + /** + * Uses floating point math to rotate a vector. + * Allows for more precise rotations. + * + * @param vector the vector to rotate + * @param degree the degree to rotate + * @return the rotated vector + */ + @NotNull + public static Vector floatRotateVector(Vector vector, int degree) { + switch (degree) { + case 270 -> { + return new Vector(DoubleUtil.invertSign(vector.getZ()), vector.getY(), vector.getX()); + } + case 180 -> { + return new Vector(DoubleUtil.invertSign(vector.getX()), vector.getY(), DoubleUtil.invertSign(vector.getZ())); + } + case 90 -> { + return new Vector(vector.getZ(), vector.getY(), DoubleUtil.invertSign(vector.getX())); + } + case 0 -> { + return vector; + } default -> { - Bukkit.getLogger().info("Invalid rotation degree"); - return null; + throw new IllegalArgumentException("Degree must be 0, 90, 180, or 270, but was " + degree); } } } diff --git a/src/main/java/us/mytheria/bloblib/utilities/WorldUtil.java b/src/main/java/us/mytheria/bloblib/utilities/WorldUtil.java new file mode 100644 index 00000000..454a107b --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/utilities/WorldUtil.java @@ -0,0 +1,25 @@ +package us.mytheria.bloblib.utilities; + +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.World; + +import java.util.LinkedList; + +public class WorldUtil { + public LinkedList getSpawnChunks(World world) { + LinkedList spawnChunks = new LinkedList<>(); + Location spawnLocation = world.getSpawnLocation(); + + int spawnChunkX = spawnLocation.getBlockX() >> 4; + int spawnChunkZ = spawnLocation.getBlockZ() >> 4; + + for (int x = spawnChunkX - 11; x <= spawnChunkX + 11; x++) { + for (int z = spawnChunkZ - 11; z <= spawnChunkZ + 11; z++) { + Chunk chunk = world.getChunkAt(x, z); + spawnChunks.add(chunk); + } + } + return spawnChunks; + } +} diff --git a/src/main/java/us/mytheria/bloblib/vault/VaultManager.java b/src/main/java/us/mytheria/bloblib/vault/VaultManager.java index 1008f5ba..867abdbe 100644 --- a/src/main/java/us/mytheria/bloblib/vault/VaultManager.java +++ b/src/main/java/us/mytheria/bloblib/vault/VaultManager.java @@ -1,41 +1,120 @@ package us.mytheria.bloblib.vault; import net.milkbowl.vault.economy.Economy; +import net.milkbowl.vault.economy.MultiEconomy; import net.milkbowl.vault.permission.Permission; import org.bukkit.Bukkit; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.server.ServiceRegisterEvent; +import org.bukkit.event.server.ServiceUnregisterEvent; import org.bukkit.plugin.RegisteredServiceProvider; -import us.mytheria.bloblib.vault.economy.AbsentEco; -import us.mytheria.bloblib.vault.economy.FoundEco; +import org.bukkit.plugin.ServicesManager; +import us.mytheria.bloblib.BlobLib; +import us.mytheria.bloblib.entities.logger.BlobPluginLogger; +import us.mytheria.bloblib.vault.economy.Absent; +import us.mytheria.bloblib.vault.economy.Found; import us.mytheria.bloblib.vault.economy.VaultEconomyWorker; +import us.mytheria.bloblib.vault.multieconomy.ElasticEconomy; import us.mytheria.bloblib.vault.permissions.AbsentPerms; import us.mytheria.bloblib.vault.permissions.FoundPerms; import us.mytheria.bloblib.vault.permissions.VaultPermissionsWorker; -public class VaultManager { - private VaultEconomyWorker vaultEconomyWorker; +public class VaultManager implements Listener { + private final BlobPluginLogger logger; + private VaultEconomyWorker vaultEconomyWorker = new Absent(); private VaultPermissionsWorker vaultPermissionsWorker; private Economy economy = null; + private ElasticEconomy elasticEconomy = new us.mytheria.bloblib.vault.multieconomy.Absent(); private Permission permission = null; private boolean vaultEcoInstalled = false; + private boolean vaultMultiEcoInstalled = false; private boolean vaultPermsInstalled = false; + private boolean singleEconomy = false; + private final ServicesManager servicesManager = Bukkit.getServicesManager(); public VaultManager() { - if (!setupEconomy()) { - Bukkit.getLogger().info("[BlobLib] Vault dependency / economy plugin not found, disabling economy features."); - vaultEconomyWorker = new AbsentEco(); - } else { - vaultEconomyWorker = new FoundEco(economy); - vaultEcoInstalled = true; + logger = BlobLib.getAnjoLogger(); + setupEconomy(); + setupPermissions(); + Bukkit.getPluginManager().registerEvents(this, BlobLib.getInstance()); + } + + @EventHandler + public void onUnregister(ServiceUnregisterEvent event) { + RegisteredServiceProvider eventProvider = event.getProvider(); + switch (eventProvider.getService().getName()) { + case "net.milkbowl.vault.economy.Economy" -> { + vaultEconomyWorker = new Absent(); + RegisteredServiceProvider provider = servicesManager.getRegistration(Economy.class); + if (provider == null) + vaultEcoInstalled = false; + else + setupEconomy(); + } + case "net.milkbowl.vault.permission.Permission" -> { + vaultPermissionsWorker = new AbsentPerms(); + RegisteredServiceProvider provider = servicesManager.getRegistration(Permission.class); + if (provider == null) + vaultPermsInstalled = false; + else + setupPermissions(); + } + case "net.milkbowl.vault.economy.MultiEconomy" -> { + elasticEconomy = new us.mytheria.bloblib.vault.multieconomy.Absent(); + RegisteredServiceProvider provider = servicesManager.getRegistration(MultiEconomy.class); + if (provider == null) { + vaultMultiEcoInstalled = false; + singleEconomy = false; + } else + setupEconomy(); + } + default -> { + } } - if (!setupPermissions()) { - Bukkit.getLogger().info("[BlobLib] Vault dependency / permissions plugin not found, disabling permissions features."); - vaultPermissionsWorker = new AbsentPerms(); - } else { - vaultPermissionsWorker = new FoundPerms(permission); - vaultPermsInstalled = true; + } + + @SuppressWarnings("unchecked") + @EventHandler + public void onRegister(ServiceRegisterEvent event) { + RegisteredServiceProvider eventProvider = event.getProvider(); + switch (eventProvider.getService().getName()) { + case "net.milkbowl.vault.economy.Economy" -> { + RegisteredServiceProvider provider = (RegisteredServiceProvider) eventProvider; + vaultEconomyWorker = new Found(provider.getProvider()); + vaultEcoInstalled = true; + Bukkit.getLogger().fine("Vault Economy override"); + } + case "net.milkbowl.vault.permission.Permission" -> { + RegisteredServiceProvider provider = (RegisteredServiceProvider) eventProvider; + vaultPermissionsWorker = new FoundPerms(provider.getProvider()); + vaultPermsInstalled = true; + Bukkit.getLogger().fine("Vault Permissions override"); + } + case "net.milkbowl.vault.economy.MultiEconomy" -> { + RegisteredServiceProvider provider = (RegisteredServiceProvider) eventProvider; + elasticEconomy = ElasticEconomy.of(provider.getProvider()); + vaultMultiEcoInstalled = true; + Bukkit.getLogger().fine("Vault MultiEconomy override"); + } + } + + if (eventProvider.getService() == Economy.class || + eventProvider.getService() == MultiEconomy.class) { + setupEconomy(); + } else if (eventProvider.getService() == Permission.class) { + setupPermissions(); } } + /** + * @return Vault Economy Worker + * @Deprecated Use {@link #getElasticEconomy()} instead + * If no MultiEconomy is provided by the Economy Plugin, + * all implementations will return the same Economy, + * which is also the default economy. + */ + @Deprecated public VaultEconomyWorker getVaultEconomyWorker() { return vaultEconomyWorker; } @@ -44,6 +123,10 @@ public VaultPermissionsWorker getVaultPermissionsWorker() { return vaultPermissionsWorker; } + public ElasticEconomy getElasticEconomy() { + return elasticEconomy; + } + public boolean isVaultEcoInstalled() { return vaultEcoInstalled; } @@ -52,21 +135,93 @@ public boolean isVaultPermsInstalled() { return vaultPermsInstalled; } - private boolean setupEconomy() { - if (Bukkit.getServer().getPluginManager().getPlugin("Vault") == null) { + public boolean isVaultMultiEcoInstalled() { + return vaultMultiEcoInstalled; + } + + public boolean isSingleEconomy() { + return singleEconomy; + } + + /** + * Determines if all packages in a String array are within the Classpath + * This is the best way to determine if a specific plugin exists and will be + * loaded. If the plugin package isn't loaded, we shouldn't bother waiting + * for it! + * + * @param packages String Array of package names to check + * @return Success or Failure + */ + private static boolean packagesExists(String... packages) { + try { + for (String pkg : packages) { + Class.forName(pkg); + } + return true; + } catch (Exception e) { return false; } - RegisteredServiceProvider rsp = Bukkit.getServer().getServicesManager().getRegistration(Economy.class); - if (rsp == null) { + } + + private boolean hasEconomyProvider() { + RegisteredServiceProvider economyServiceProvider = servicesManager.getRegistration(Economy.class); + if (economyServiceProvider == null) + return false; + vaultEcoInstalled = true; + economy = economyServiceProvider.getProvider(); + vaultEconomyWorker = new Found(economy); + return true; + } + + private boolean hasMultiEconomyProvider() { + if (!packagesExists("net.milkbowl.vault.economy.MultiEconomy")) { + Bukkit.getLogger().info("Not using Vault2, disabling MultiEconomy features"); return false; } - economy = rsp.getProvider(); - return economy != null; + Bukkit.getLogger().info("Detected Vault2, enabling MultiEconomy features"); + RegisteredServiceProvider multieconomyServiceProvider = servicesManager.getRegistration(MultiEconomy.class); + if (multieconomyServiceProvider == null) { + if (!vaultEcoInstalled) + return false; + RegisteredServiceProvider economyServiceProvider = servicesManager.getRegistration(Economy.class); + ElasticEconomy legacy = ElasticEconomy.of(economyServiceProvider.getProvider()); + if (legacy == null) + return false; + elasticEconomy = legacy; + vaultMultiEcoInstalled = true; + singleEconomy = true; + return true; + } + ElasticEconomy multi = ElasticEconomy.of(multieconomyServiceProvider.getProvider()); + if (multi == null) + return false; + vaultMultiEcoInstalled = true; + elasticEconomy = multi; + return true; } - private boolean setupPermissions() { + private boolean hasPermissionsProvider() { RegisteredServiceProvider rsp = Bukkit.getServer().getServicesManager().getRegistration(Permission.class); + if (rsp == null) + return false; permission = rsp.getProvider(); return permission != null; } + + public void setupEconomy() { + if (Bukkit.getServer().getPluginManager().getPlugin("Vault") == null) + return; + hasEconomyProvider(); + hasMultiEconomyProvider(); + } + + public void setupPermissions() { + if (!hasPermissionsProvider()) { + logger.log("Vault dependency / permissions plugin not found, disabling permissions features."); + vaultPermissionsWorker = new AbsentPerms(); + } else { + vaultPermissionsWorker = new FoundPerms(permission); + vaultPermsInstalled = true; + } + } } diff --git a/src/main/java/us/mytheria/bloblib/vault/economy/AbsentEco.java b/src/main/java/us/mytheria/bloblib/vault/economy/Absent.java similarity index 80% rename from src/main/java/us/mytheria/bloblib/vault/economy/AbsentEco.java rename to src/main/java/us/mytheria/bloblib/vault/economy/Absent.java index 367d47d5..c7a1b93c 100644 --- a/src/main/java/us/mytheria/bloblib/vault/economy/AbsentEco.java +++ b/src/main/java/us/mytheria/bloblib/vault/economy/Absent.java @@ -6,7 +6,12 @@ * A class that does nothing since there is no * economy plugin compatible with Vault. */ -public class AbsentEco implements VaultEconomyWorker { +public class Absent implements VaultEconomyWorker { + + @Override + public String getName() { + return ""; + } /** * Does nothing since there is no economy plugin. @@ -60,4 +65,15 @@ public boolean hasCashAmount(Player player, double amount) { public double getCash(Player player) { return 0; } + + /** + * Does nothing since there is no economy plugin. + * + * @param amount Amount to format + * @return 0 + */ + @Override + public String format(double amount) { + return amount + ""; + } } diff --git a/src/main/java/us/mytheria/bloblib/vault/economy/FoundEco.java b/src/main/java/us/mytheria/bloblib/vault/economy/Found.java similarity index 79% rename from src/main/java/us/mytheria/bloblib/vault/economy/FoundEco.java rename to src/main/java/us/mytheria/bloblib/vault/economy/Found.java index 7f5554d3..f4a3de51 100644 --- a/src/main/java/us/mytheria/bloblib/vault/economy/FoundEco.java +++ b/src/main/java/us/mytheria/bloblib/vault/economy/Found.java @@ -3,13 +3,18 @@ import net.milkbowl.vault.economy.Economy; import org.bukkit.entity.Player; -public class FoundEco implements VaultEconomyWorker { +public class Found implements VaultEconomyWorker { private final Economy economy; - public FoundEco(Economy economy) { + public Found(Economy economy) { this.economy = economy; } + @Override + public String getName() { + return economy.getName(); + } + @Override public void addCash(Player player, double amount) { economy.depositPlayer(player, amount); @@ -42,4 +47,9 @@ public boolean hasCashAmount(Player player, double amount) { public double getCash(Player player) { return economy.getBalance(player); } + + @Override + public String format(double amount) { + return economy.format(amount); + } } diff --git a/src/main/java/us/mytheria/bloblib/vault/economy/VaultEconomyWorker.java b/src/main/java/us/mytheria/bloblib/vault/economy/VaultEconomyWorker.java index 62da7839..2e6a1c2c 100644 --- a/src/main/java/us/mytheria/bloblib/vault/economy/VaultEconomyWorker.java +++ b/src/main/java/us/mytheria/bloblib/vault/economy/VaultEconomyWorker.java @@ -4,6 +4,15 @@ public interface VaultEconomyWorker { + /** + * In case of being an economy from MultiEconomy, it is expected to return + * the currency name. + * Else it's the name of the economy plugin or the name of the economy implementation. + * + * @return Name of the currency + */ + String getName(); + /** * Gives the given amount of cash to the player. * @@ -44,4 +53,12 @@ public interface VaultEconomyWorker { * @return Amount of cash the player has */ double getCash(Player player); + + /** + * Formats the given amount of cash. + * + * @param amount Amount to format + * @return Formatted amount + */ + String format(double amount); } diff --git a/src/main/java/us/mytheria/bloblib/vault/multieconomy/Absent.java b/src/main/java/us/mytheria/bloblib/vault/multieconomy/Absent.java new file mode 100644 index 00000000..bc229498 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/vault/multieconomy/Absent.java @@ -0,0 +1,61 @@ +package us.mytheria.bloblib.vault.multieconomy; + +import net.milkbowl.vault.economy.IdentityEconomy; + +import java.util.Collection; + +/** + * A class that does nothing since there is no + * economy plugin compatible with Vault. + */ +public class Absent extends ElasticEconomy { + public Absent() { + super(null, + ElasticEconomyType.ABSENT); + } + + @Override + public boolean isEnabled() { + return false; + } + + @Override + public String getName() { + return null; + } + + @Override + public boolean existsImplementation(String name) { + return false; + } + + @Override + public boolean existsImplementation(String name, String world) { + return false; + } + + @Override + public IdentityEconomy getImplementation(String name) { + return null; + } + + @Override + public IdentityEconomy getDefault() { + return null; + } + + @Override + public IdentityEconomy getDefault(String world) { + return null; + } + + @Override + public Collection getAllImplementations() { + return null; + } + + @Override + public Collection getAllImplementations(String world) { + return null; + } +} diff --git a/src/main/java/us/mytheria/bloblib/vault/multieconomy/ElasticEconomy.java b/src/main/java/us/mytheria/bloblib/vault/multieconomy/ElasticEconomy.java new file mode 100644 index 00000000..1ef364b9 --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/vault/multieconomy/ElasticEconomy.java @@ -0,0 +1,123 @@ +package us.mytheria.bloblib.vault.multieconomy; + +import net.milkbowl.vault.economy.Economy; +import net.milkbowl.vault.economy.IdentityEconomy; +import net.milkbowl.vault.economy.MultiEconomy; + +import java.util.Collection; +import java.util.Optional; + +/** + * Allows both MultiEconomy and legacy Economy (in an instance of SingleEconomy) + * to be used without needing to implement different abstractions in each plugin. + */ +public class ElasticEconomy implements MultiEconomy { + private final MultiEconomy economy; + private final ElasticEconomyType type; + + public static ElasticEconomy of(Economy economy) { + return new ElasticEconomy(SingleEconomy.fromLegacy(economy), ElasticEconomyType.SINGLE); + } + + public static ElasticEconomy of(MultiEconomy economy) { + return new ElasticEconomy(economy, ElasticEconomyType.MULTI); + } + + public static ElasticEconomy absent() { + return new ElasticEconomy(new Absent(), ElasticEconomyType.ABSENT); + } + + /** + * Creates a new ElasticEconomy instance. + * + * @param economy The economy to use. + * @param type The type of economy. + */ + protected ElasticEconomy(MultiEconomy economy, + ElasticEconomyType type) { + this.economy = economy; + this.type = type; + } + + @Override + public boolean isEnabled() { + return economy.isEnabled(); + } + + @Override + public String getName() { + return economy.getName(); + } + + @Override + public boolean existsImplementation(String name) { + return economy.existsImplementation(name); + } + + @Override + public boolean existsImplementation(String name, String world) { + return economy.existsImplementation(name, world); + } + + @Override + public IdentityEconomy getImplementation(String name) { + return economy.getImplementation(name); + } + + @Override + public IdentityEconomy getDefault() { + return economy.getDefault(); + } + + @Override + public IdentityEconomy getDefault(String world) { + return economy.getDefault(world); + } + + @Override + public Collection getAllImplementations() { + return economy.getAllImplementations(); + } + + @Override + public Collection getAllImplementations(String world) { + return economy.getAllImplementations(world); + } + + /** + * If true, all implementations, even the default one, will return the same. + * + * @return true if instance of SingleEconomy + */ + public boolean isSingleEconomy() { + return type == ElasticEconomyType.SINGLE; + } + + /** + * If true, there is no provider for legacy Economy and MultiEconomy. + * This case (returning true), can only be given if there's no economy Vault compatible + * economy plugin or if the economy plugin developer/author didn't provide to Vault. + * + * @return true if there's no Vault economy provider + */ + public boolean isAbsent() { + return type == ElasticEconomyType.ABSENT; + } + + /** + * If true, it means that the economy plugin/provider does actually handle multi-currency. + * + * @return true if economy provider handles multi-currency + */ + public boolean isMultiEconomy() { + return type == ElasticEconomyType.MULTI; + } + + public ElasticEconomyType getType() { + return type; + } + + public IdentityEconomy map(Optional implementation) { + return implementation.map(this::getImplementation).orElseGet(this::getDefault); + } +} diff --git a/src/main/java/us/mytheria/bloblib/vault/multieconomy/ElasticEconomyType.java b/src/main/java/us/mytheria/bloblib/vault/multieconomy/ElasticEconomyType.java new file mode 100644 index 00000000..4e66a01d --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/vault/multieconomy/ElasticEconomyType.java @@ -0,0 +1,7 @@ +package us.mytheria.bloblib.vault.multieconomy; + +public enum ElasticEconomyType { + ABSENT, + SINGLE, + MULTI +} diff --git a/src/main/java/us/mytheria/bloblib/vault/multieconomy/SingleEconomy.java b/src/main/java/us/mytheria/bloblib/vault/multieconomy/SingleEconomy.java new file mode 100644 index 00000000..3abf5d8b --- /dev/null +++ b/src/main/java/us/mytheria/bloblib/vault/multieconomy/SingleEconomy.java @@ -0,0 +1,71 @@ +package us.mytheria.bloblib.vault.multieconomy; + +import net.milkbowl.vault.economy.Economy; +import net.milkbowl.vault.economy.IdentityEconomy; +import net.milkbowl.vault.economy.MultiEconomy; +import net.milkbowl.vault.economy.wrappers.EconomyWrapper; + +import java.util.Collection; +import java.util.List; + +/** + * Allows using MultiEconomy class even if only Economy/IdentityEconomy is provided + * I would ship this inside VaultAPI2, but I am not sure if most developers would use it... + */ +public class SingleEconomy implements MultiEconomy { + private final IdentityEconomy economy; + + public static SingleEconomy fromLegacy(Economy economy) { + EconomyWrapper wrapper = new EconomyWrapper(economy); + return new SingleEconomy(wrapper.legacy()); + } + + public SingleEconomy(IdentityEconomy economy) { + this.economy = economy; + } + + @Override + public boolean isEnabled() { + return economy.isEnabled(); + } + + @Override + public String getName() { + return economy.getName(); + } + + @Override + public boolean existsImplementation(String name) { + return true; + } + + @Override + public boolean existsImplementation(String name, String world) { + return true; + } + + @Override + public IdentityEconomy getImplementation(String name) { + return economy; + } + + @Override + public IdentityEconomy getDefault() { + return economy; + } + + @Override + public IdentityEconomy getDefault(String world) { + return economy; + } + + @Override + public Collection getAllImplementations() { + return List.of(economy); + } + + @Override + public Collection getAllImplementations(String world) { + return getAllImplementations(); + } +} diff --git a/src/main/resources/CurrencyBuilder.yml b/src/main/resources/CurrencyBuilder.yml index 9811780d..e71435a7 100644 --- a/src/main/resources/CurrencyBuilder.yml +++ b/src/main/resources/CurrencyBuilder.yml @@ -29,7 +29,7 @@ Buttons: CustomModelData: 0 DisplayName: "&6Display" Lore: - - "&7Selected: &f%display%" + - "&7Selected: &f%itemStack%" Slot: 3 InitialBalance: ItemStack: @@ -63,4 +63,4 @@ Buttons: Lore: - "&7Click to add reward" - "&7to the files." - Slot: 13 \ No newline at end of file + Slot: 22 \ No newline at end of file diff --git a/src/main/resources/bloblib_actions.yml b/src/main/resources/bloblib_actions.yml new file mode 100644 index 00000000..e2c53d36 --- /dev/null +++ b/src/main/resources/bloblib_actions.yml @@ -0,0 +1,10 @@ +Gamemode: + Creative: + Type: CONSOLE_COMMAND + Command: 'gamemode creative %actor%' + Survival: + Type: CONSOLE_COMMAND + Command: 'gamemode survival %actor%' + Adventure: + Type: CONSOLE_COMMAND + Command: 'gamemode survival %actor%' \ No newline at end of file diff --git a/src/main/resources/bloblib_inventories.yml b/src/main/resources/bloblib_inventories.yml index c1e4b692..d2f4275b 100644 --- a/src/main/resources/bloblib_inventories.yml +++ b/src/main/resources/bloblib_inventories.yml @@ -23,7 +23,7 @@ VariableSelector: DisplayName: "&6Previous page" Lore: - "&7Click to go to the previous page" - Slot: 48 + Slot: 52 Next-Page: ItemStack: Material: ARROW @@ -31,4 +31,54 @@ VariableSelector: DisplayName: "&6Next page" Lore: - "&7Click to go to the next page" - Slot: 50 \ No newline at end of file + Slot: 53 +BlobEditor: + Title: '&l%variable% > VIEW' + Size: 54 + Buttons: + Background: + ItemStack: + Material: BLACK_STAINED_GLASS_PANE + CustomModelData: 0 + DisplayName: " " + Lore: [ ] + Slot: 45-53 + White-Background: + ItemStack: + Material: WHITE_STAINED_GLASS_PANE + CustomModelData: 0 + DisplayName: " " + Lore: [ ] + Slot: 0-44 + Add: + ItemStack: + Material: ANVIL + CustomModelData: 0 + DisplayName: "&6Add" + Lore: + - "&7Click to &aadd &7a new element" + Slot: 49 + Remove: + ItemStack: + Material: GRINDSTONE + CustomModelData: 0 + DisplayName: "&6Remove" + Lore: + - "&7Click to &cremove &7an existing element" + Slot: 50 + Previous-Page: + ItemStack: + Material: ARROW + CustomModelData: 0 + DisplayName: "&6Previous page" + Lore: + - "&7Click to go to the previous page" + Slot: 52 + Next-Page: + ItemStack: + Material: ARROW + CustomModelData: 0 + DisplayName: "&6Next page" + Lore: + - "&7Click to go to the next page" + Slot: 53 \ No newline at end of file diff --git a/src/main/resources/bloblib_lang.yml b/src/main/resources/bloblib_lang.yml index 9e60bda7..8e26cbbf 100644 --- a/src/main/resources/bloblib_lang.yml +++ b/src/main/resources/bloblib_lang.yml @@ -1,4 +1,34 @@ +BlobLib: + Updater-Available: + Type: CHAT + Message: '&7A new version &8(%version%) &7of %randomColor%%plugin% &7is available. You may type %randomColor%''/bloblib update %plugin%''' + BlobSound: System.Alert + Updater-Successful: + Type: CHAT + Message: '%randomColor%%plugin% &7has been updated to %randomColor%%version%&7. You may restart the server now' + BlobSound: System.Done + Download-Usage: + Type: CHAT + Message: '&7Usage: &8/bloblib download ' + Download-GitHub-Usage: + Type: CHAT + Message: '&7Usage: &8/bloblib download GitHub ' + Download-GitHub-Successful: + Type: CHAT + Message: '&7Successfully downloaded %randomColor%%fileName%&7. You may restart the server now' + Repository-Not-Found: + Type: CHAT + Message: '&cRepository not found' + BlobSound: System.Alert + No-Connection: + Type: CHAT + Message: '&cFailed to connect to the destination. Check your internet connection' + BlobSound: System.Alert System: + Error: + Type: CHAT + Message: '&cAn error has occurred. Please contact the server administrators.' + BlobSound: System.Alert No-Permission: Type: CHAT Message: '&cI''m sorry, but you do not have permission to perform this command. @@ -18,6 +48,10 @@ System: Type: CHAT Message: '&cCannot proceed. We are expecting you to select something else.' BlobSound: System.Alert + Already-Editor-Listening: + Type: CHAT + Message: '&cCannot proceed. We are expecting you to select something else.' + BlobSound: System.Alert Already-Drop-Listening: Type: CHAT Message: '&cCannot proceed. We are expecting you to drop something else.' @@ -27,6 +61,13 @@ System: Title: '&f&lDONE' Subtitle: '&7Reload complete' BlobSound: System.Done +Algorithm: + Adjacent-Null: + Type: ACTIONBAR_TITLE + Title: '&c&lERROR' + Subtitle: '&7Cannot proceed' + Actionbar: "&cCouldn't find adyacent block" + BlobSound: System.Alert Object: Not-Found: Type: CHAT @@ -42,6 +83,22 @@ Builder: Type: CHAT Message: '&cPlease enter a valid number' BlobSound: System.Alert + Location-Target: + Type: ACTIONBAR + Message: '&7Click a block. Selected block will be used' + Location-Target-Timeout: + Type: TITLE + Title: '&c&lTIMEOUT' + Subtitle: '&7You took too long to select a location' + BlobSound: Builder.Timeout + Location: + Type: ACTIONBAR + Message: '&7Click a block. Block above it will be used' + Location-Timeout: + Type: TITLE + Title: '&c&lTIMEOUT' + Subtitle: '&7You took too long to select a location' + BlobSound: Builder.Timeout World-Not-Found: Type: CHAT Message: "&cWorld '%world%' not found" @@ -59,11 +116,6 @@ Builder: Title: '&c&lTIMEOUT' Subtitle: '&7You took too long to type in a world' BlobSound: Builder.Timeout - Position-Timeout: - Type: TITLE - Title: '&c&lTIMEOUT' - Subtitle: '&7You took too long to select a position' - BlobSound: Builder.Timeout Material: Type: TITLE Title: '&f&lMATERIAL' @@ -131,6 +183,24 @@ Builder: Title: '&c&lTIMEOUT' Subtitle: '&7You took too long to type in Message' BlobSound: Builder.Timeout + BuyingCurrency: + Type: TITLE + Title: '&f&lBUYING CURRENCY' + Subtitle: '&7Type in a currency name' + BuyingCurrency-Timeout: + Type: TITLE + Title: '&c&lTIMEOUT' + Subtitle: '&7You took too long to type in BuyingCurrency' + BlobSound: Builder.Timeout + SellingCurrency: + Type: TITLE + Title: '&f&lSELLING CURRENCY' + Subtitle: '&7Type in a currency name' + SellingCurrency-Timeout: + Type: TITLE + Title: '&c&lTIMEOUT' + Subtitle: '&7You took too long to type in SellingCurrency' + BlobSound: Builder.Timeout Editor: Remove: Type: ACTIONBAR @@ -139,6 +209,9 @@ Editor: Type: TITLE Title: '&f&lREMOVED' Subtitle: '&7%element%' + Select: + Type: ACTIONBAR + Message: '&7Click an element to select it' Player: Not-Found: Type: CHAT @@ -159,6 +232,14 @@ Currency: Message: '&cCurrency not found' BlobSound: System.Alert Economy: + Received-Deposit: + Type: CHAT + Message: '&7+ %display%' + BlobSound: Economy.Received-Deposit + Received-Withdrawal: + Type: CHAT + Message: '&7- %display%' + BlobSound: Economy.Received-Withdrawal Deposit: Type: CHAT Message: '&7Gave %display% %currency% &7to %player%' diff --git a/src/main/resources/bloblib_meta_inventories.yml b/src/main/resources/bloblib_meta_inventories.yml new file mode 100644 index 00000000..e69de29b diff --git a/src/main/resources/bloblib_sounds.yml b/src/main/resources/bloblib_sounds.yml index ae1be032..58e1007a 100644 --- a/src/main/resources/bloblib_sounds.yml +++ b/src/main/resources/bloblib_sounds.yml @@ -3,20 +3,36 @@ System: Sound: BLOCK_NOTE_BLOCK_PLING Volume: 1.0 Pitch: 0.9 + Audience: PLAYER Done: Sound: ENTITY_ARROW_HIT_PLAYER Volume: 1.0 Pitch: 0.9 + Audience: PLAYER Builder: Button-Click: Sound: UI_BUTTON_CLICK Volume: 1.0 Pitch: 1.0 + Audience: PLAYER Build-Complete: Sound: BLOCK_ANVIL_USE Volume: 1.0 Pitch: 1.35 + Audience: PLAYER Timeout: Sound: BLOCK_NOTE_BLOCK_FLUTE Volume: 1.0 - Pitch: 0.9 \ No newline at end of file + Pitch: 0.9 + Audience: PLAYER +Economy: + Received-Deposit: + Sound: BLOCK_NOTE_BLOCK_PLING + Volume: 1.0 + Pitch: 2.0 + Audience: PLAYER + Received-Withdrawal: + Sound: BLOCK_NOTE_BLOCK_PLING + Volume: 1.0 + Pitch: 0.85 + Audience: PLAYER \ No newline at end of file diff --git a/src/main/resources/bungee.yml b/src/main/resources/bungee.yml deleted file mode 100644 index 709ee70a..00000000 --- a/src/main/resources/bungee.yml +++ /dev/null @@ -1,4 +0,0 @@ -name: BlobLib -main: us.mytheria.bloblib.BlobLibBungee -version: ${project.version}.${buildNumber} -author: promorrom \ 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 00000000..38a90efc --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1 @@ +Display-Unriding: true \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 09e16817..bd5047a9 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,9 +1,9 @@ name: BlobLib version: ${project.version} main: us.mytheria.bloblib.BlobLib -api-version: 1.16 +api-version: 1.17 load: STARTUP -softdepend: [ WorldGuard, WorldEdit, Vault ] +softdepend: [ WorldGuard, WorldEdit, Vault, LibsDisguises, ProtocolLib ] authors: [ promorrom, j0ach1mmall3, Dean B, FourteenBrush, SmallCode ] commands: bloblib: