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