diff --git a/.gitignore b/.gitignore index ccdc6b0..fb709ac 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ build/ !gradle/wrapper/gradle-wrapper.jar !**/src/main/**/build/ !**/src/test/**/build/ +target-api/ +target/ +destinations.yml ### IntelliJ IDEA ### .idea/modules.xml diff --git a/API/src/main/java/fr/maxlego08/items/api/ItemComponent.java b/API/src/main/java/fr/maxlego08/items/api/ItemComponent.java index 242c5f7..86074a4 100644 --- a/API/src/main/java/fr/maxlego08/items/api/ItemComponent.java +++ b/API/src/main/java/fr/maxlego08/items/api/ItemComponent.java @@ -24,4 +24,8 @@ public interface ItemComponent { void sendItemLore(Player player, ItemMeta itemMeta); void sendMessage(CommandSender sender, String string); + + void sendActionBar(Player player, String message); + + void sendTitle(Player player, String title, String subtitle, int fadeInTime, int showTime, int fadeOutTime); } diff --git a/API/src/main/java/fr/maxlego08/items/api/ItemPlugin.java b/API/src/main/java/fr/maxlego08/items/api/ItemPlugin.java index 5c4db73..acd7d7a 100644 --- a/API/src/main/java/fr/maxlego08/items/api/ItemPlugin.java +++ b/API/src/main/java/fr/maxlego08/items/api/ItemPlugin.java @@ -25,14 +25,14 @@ public interface ItemPlugin extends Plugin { ItemManager getItemManager(); - PlatformScheduler getScheduler(); - RecipesAPI getRecipesAPI(); HookManager getHookManager(); RuneManager getRuneManager(); + ItemComponent getItemComponent(); + List getBlockAccess(); void registerBlockAccess(BlockAccess blockAccess); diff --git a/API/src/main/java/fr/maxlego08/items/api/configurations/ItemConfiguration.java b/API/src/main/java/fr/maxlego08/items/api/configurations/ItemConfiguration.java index 01c8d2e..e4f2c13 100644 --- a/API/src/main/java/fr/maxlego08/items/api/configurations/ItemConfiguration.java +++ b/API/src/main/java/fr/maxlego08/items/api/configurations/ItemConfiguration.java @@ -6,23 +6,10 @@ import fr.maxlego08.items.api.ItemPlugin; import fr.maxlego08.items.api.ItemType; import fr.maxlego08.items.api.configurations.commands.CommandsConfiguration; -import fr.maxlego08.items.api.configurations.meta.ArmorStandConfig; -import fr.maxlego08.items.api.configurations.meta.AttributeConfiguration; -import fr.maxlego08.items.api.configurations.meta.AxolotlBucketConfiguration; -import fr.maxlego08.items.api.configurations.meta.BannerMetaConfiguration; -import fr.maxlego08.items.api.configurations.meta.BlockDataMetaConfiguration; -import fr.maxlego08.items.api.configurations.meta.BlockStateMetaConfiguration; -import fr.maxlego08.items.api.configurations.meta.CustomPotionEffect; -import fr.maxlego08.items.api.configurations.meta.Food; -import fr.maxlego08.items.api.configurations.meta.FoodEffect; -import fr.maxlego08.items.api.configurations.meta.ItemEnchantment; -import fr.maxlego08.items.api.configurations.meta.LeatherArmorMetaConfiguration; -import fr.maxlego08.items.api.configurations.meta.PotionMetaConfiguration; -import fr.maxlego08.items.api.configurations.meta.ToolComponentConfiguration; -import fr.maxlego08.items.api.configurations.meta.TrimConfiguration; +import fr.maxlego08.items.api.configurations.meta.*; import fr.maxlego08.items.api.enchantments.EnchantmentRegistry; import fr.maxlego08.items.api.enchantments.Enchantments; -import fr.maxlego08.items.api.recipes.ZRecipeConfiguration; +import fr.maxlego08.items.api.configurations.recipes.ZRecipeConfiguration; import fr.maxlego08.items.api.runes.ItemRuneConfiguration; import fr.maxlego08.items.api.runes.Rune; import fr.maxlego08.items.api.utils.Colors; @@ -43,15 +30,7 @@ import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.EquipmentSlotGroup; import org.bukkit.inventory.ItemRarity; -import org.bukkit.inventory.meta.ArmorMeta; -import org.bukkit.inventory.meta.AxolotlBucketMeta; -import org.bukkit.inventory.meta.BannerMeta; -import org.bukkit.inventory.meta.BlockDataMeta; -import org.bukkit.inventory.meta.BlockStateMeta; -import org.bukkit.inventory.meta.EnchantmentStorageMeta; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.inventory.meta.LeatherArmorMeta; -import org.bukkit.inventory.meta.PotionMeta; +import org.bukkit.inventory.meta.*; import org.bukkit.inventory.meta.components.ToolComponent; import org.bukkit.inventory.meta.trim.ArmorTrim; import org.bukkit.inventory.meta.trim.TrimMaterial; @@ -337,8 +316,7 @@ private void loadPotion(ItemPlugin plugin, YamlConfiguration configuration, Stri this.potionMetaConfiguration = new PotionMetaConfiguration(true, potionColor, customEffects, basePotionType); } catch (Exception exception) { - plugin.getLogger().severe("Impossible to load the potion meta for item " + fileName); - exception.printStackTrace(); + plugin.getLogger().severe("Failed to load potion meta for item " + fileName + ": " + exception.getMessage()); this.potionMetaConfiguration = new PotionMetaConfiguration(false, null, null, null); } } else { @@ -606,7 +584,7 @@ public void applyLeatherArmorMeta(ItemMeta itemMeta) { public void createRecipe(ItemPlugin plugin, Item item) { if (configuration.contains("recipes")) { for (String key : configuration.getConfigurationSection("recipes").getKeys(false)) { - var recipeConfig = new ZRecipeConfiguration(plugin, key, "recipes." + key, configuration); + var recipeConfig = new ZRecipeConfiguration(key, "recipes." + key, configuration); recipeConfig.setResult(item.build(null, 1)); var recipe = recipeConfig.build(); this.recipes.add(recipe); diff --git a/API/src/main/java/fr/maxlego08/items/api/configurations/meta/BlockDataMetaConfiguration.java b/API/src/main/java/fr/maxlego08/items/api/configurations/meta/BlockDataMetaConfiguration.java index 220d004..cb5289c 100644 --- a/API/src/main/java/fr/maxlego08/items/api/configurations/meta/BlockDataMetaConfiguration.java +++ b/API/src/main/java/fr/maxlego08/items/api/configurations/meta/BlockDataMetaConfiguration.java @@ -15,6 +15,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.logging.Level; public record BlockDataMetaConfiguration(boolean enable, BlockData blockData) { @@ -390,8 +391,7 @@ public static BlockDataMetaConfiguration loadBlockDataMeta(ItemPlugin plugin, Ya } catch (Exception exception) { - plugin.getLogger().severe("Invalid block data or material configuration in " + fileName); - exception.printStackTrace(); + plugin.getLogger().log(Level.SEVERE, "Invalid block data or material configuration in file: " + fileName, exception); enableBlockDataMeta = false; } return new BlockDataMetaConfiguration(enableBlockDataMeta, blockData); diff --git a/API/src/main/java/fr/maxlego08/items/api/configurations/meta/BlockStateMetaConfiguration.java b/API/src/main/java/fr/maxlego08/items/api/configurations/meta/BlockStateMetaConfiguration.java index 66fa043..c307da3 100644 --- a/API/src/main/java/fr/maxlego08/items/api/configurations/meta/BlockStateMetaConfiguration.java +++ b/API/src/main/java/fr/maxlego08/items/api/configurations/meta/BlockStateMetaConfiguration.java @@ -3,11 +3,7 @@ import fr.maxlego08.items.api.ItemComponent; import fr.maxlego08.items.api.ItemPlugin; import fr.maxlego08.items.api.configurations.ItemConfiguration; -import fr.maxlego08.items.api.configurations.state.ItemSlot; -import fr.maxlego08.items.api.configurations.state.ItemSlotCustomItem; -import fr.maxlego08.items.api.configurations.state.ItemSlotItem; -import fr.maxlego08.items.api.configurations.state.SignConfiguration; -import fr.maxlego08.items.api.configurations.state.SignLine; +import fr.maxlego08.items.api.configurations.state.*; import org.bukkit.block.BlockState; import org.bukkit.block.Container; import org.bukkit.block.Sign; diff --git a/API/src/main/java/fr/maxlego08/items/api/configurations/meta/ToolComponentConfiguration.java b/API/src/main/java/fr/maxlego08/items/api/configurations/meta/ToolComponentConfiguration.java index 69c5afc..4051209 100644 --- a/API/src/main/java/fr/maxlego08/items/api/configurations/meta/ToolComponentConfiguration.java +++ b/API/src/main/java/fr/maxlego08/items/api/configurations/meta/ToolComponentConfiguration.java @@ -10,6 +10,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.logging.Level; public record ToolComponentConfiguration(boolean enable, int damagePerBlock, float defaultMiningSpeed, List toolRuleTags, List toolMaterialsTags) { @@ -68,8 +69,7 @@ public static ToolComponentConfiguration loadToolComponent(ItemPlugin plugin, Ya return new ToolComponentConfiguration(true, damagePerBlock, defaultMiningSpeed, toolRuleTags, toolMaterialTags); } catch (Exception exception) { - plugin.getLogger().severe("Invalid tool component configuration in " + fileName); - exception.printStackTrace(); + plugin.getLogger().log(Level.SEVERE, "Invalid tool component configuration in file: " + fileName, exception); } } return new ToolComponentConfiguration(false, 0, 0f, null, null); diff --git a/API/src/main/java/fr/maxlego08/items/api/recipes/ZItemHook.java b/API/src/main/java/fr/maxlego08/items/api/recipes/ZItemHook.java index 7fed4a2..7ddffa1 100644 --- a/API/src/main/java/fr/maxlego08/items/api/recipes/ZItemHook.java +++ b/API/src/main/java/fr/maxlego08/items/api/recipes/ZItemHook.java @@ -1,7 +1,7 @@ package fr.maxlego08.items.api.recipes; import fr.maxlego08.items.api.ItemPlugin; -import fr.traqueur.recipes.api.domains.BaseIngredient; +import fr.traqueur.recipes.api.domains.Ingredient; import fr.traqueur.recipes.api.hook.Hook; import org.bukkit.inventory.ItemStack; @@ -19,7 +19,7 @@ public String getPluginName() { } @Override - public BaseIngredient getIngredient(String s, Character character) { + public Ingredient getIngredient(String s, Character character) { return new ZItemIngredient(s, character); } diff --git a/API/src/main/java/fr/maxlego08/items/api/recipes/ZItemIngredient.java b/API/src/main/java/fr/maxlego08/items/api/recipes/ZItemIngredient.java index 1c52297..46a05b7 100644 --- a/API/src/main/java/fr/maxlego08/items/api/recipes/ZItemIngredient.java +++ b/API/src/main/java/fr/maxlego08/items/api/recipes/ZItemIngredient.java @@ -2,7 +2,7 @@ import fr.maxlego08.items.api.Item; import fr.maxlego08.items.api.ItemManager; -import fr.traqueur.recipes.api.domains.BaseIngredient; +import fr.traqueur.recipes.api.domains.Ingredient; import org.bukkit.Bukkit; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.RecipeChoice; @@ -10,7 +10,7 @@ import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataType; -public class ZItemIngredient extends BaseIngredient { +public class ZItemIngredient extends Ingredient { private final Item item; diff --git a/API/src/main/java/fr/maxlego08/items/api/recipes/ZRecipeConfiguration.java b/API/src/main/java/fr/maxlego08/items/api/recipes/ZRecipeConfiguration.java index 440b101..9e25a7e 100644 --- a/API/src/main/java/fr/maxlego08/items/api/recipes/ZRecipeConfiguration.java +++ b/API/src/main/java/fr/maxlego08/items/api/recipes/ZRecipeConfiguration.java @@ -1,6 +1,5 @@ -package fr.maxlego08.items.api.recipes; +package fr.maxlego08.items.api.configurations.recipes; -import fr.traqueur.recipes.api.Base64; import fr.traqueur.recipes.api.RecipeType; import fr.traqueur.recipes.api.TagRegistry; import fr.traqueur.recipes.api.domains.Ingredient; @@ -19,10 +18,14 @@ import org.bukkit.inventory.recipe.CraftingBookCategory; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.util.io.BukkitObjectInputStream; +import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.util.*; +import java.util.zip.GZIPInputStream; public class ZRecipeConfiguration implements Recipe { /** @@ -70,25 +73,13 @@ public class ZRecipeConfiguration implements Recipe { /** * The constructor of the recipe. * - * @param plugin the plugin of the recipe. - * @param name the name of the recipe. - * @param configuration the configuration of the recipe. - */ - public ZRecipeConfiguration(JavaPlugin plugin, String name, YamlConfiguration configuration) { - this(plugin, name, "", configuration); - } - - /** - * The constructor of the recipe. - * - * @param plugin the plugin of the recipe. * @param name the name of the recipe. * @param path the path of the recipe. * @param configuration the configuration of the recipe. */ - public ZRecipeConfiguration(Plugin plugin, String name, String path, YamlConfiguration configuration) { + public ZRecipeConfiguration(String name, String path, YamlConfiguration configuration) { this.name = name.replace(".yml", ""); - if (!path.endsWith(".") && !path.isEmpty()) { + if(!path.endsWith(".") && !path.isEmpty()) { path += "."; } String strType = configuration.getString(path + "type", "ERROR"); @@ -99,25 +90,26 @@ public ZRecipeConfiguration(Plugin plugin, String name, String path, YamlConfigu } this.category = configuration.getString(path + "category", ""); this.group = configuration.getString(path + "group", ""); - if (!this.checkGategory(this.category)) { + if(!this.checkCategory(this.category)) { throw new IllegalArgumentException("The category " + this.category + " isn't valid."); } - if (configuration.contains(path + "pattern")) { - this.pattern = configuration.getStringList(path + "pattern").toArray(new String[0]); + if(configuration.contains(path + "pattern")) { + this.pattern = configuration.getStringList(path+"pattern").toArray(new String[0]); + this.validatePattern(); } - if (!configuration.contains(path + "ingredients")) { + if(!configuration.contains(path + "ingredients")) { throw new IllegalArgumentException("The recipe " + name + " doesn't have ingredients."); } - for (Map ingredient : configuration.getMapList(path + "ingredients")) { + for(Map ingredient : configuration.getMapList(path + "ingredients")) { String material = (String) ingredient.get("item"); var objSign = ingredient.getOrDefault("sign", null); Character sign = objSign == null ? null : objSign.toString().charAt(0); String[] data = material.split(":"); - if (data.length == 1) { + if(data.length == 1) { this.ingredientList.add(new MaterialIngredient(this.getMaterial(data[0]), sign)); } else { Ingredient ingred = switch (data[0]) { @@ -125,13 +117,13 @@ public ZRecipeConfiguration(Plugin plugin, String name, String path, YamlConfigu case "tag" -> new TagIngredient(this.getTag(data[1]), sign); case "item" -> { boolean strict = this.isStrict(ingredient); - if (strict) { + if(strict) { yield new StrictItemStackIngredient(this.getItemStack(data[1]), sign); } yield new ItemStackIngredient(this.getItemStack(data[1]), sign); } default -> Hook.HOOKS.stream() - .filter(hook -> plugin.getServer().getPluginManager().isPluginEnabled(hook.getPluginName())) + .filter(Hook::isEnable) .filter(hook -> hook.getPluginName().equalsIgnoreCase(data[0])) .findFirst() .orElseThrow(() -> new IllegalArgumentException("The data " + data[0] + " isn't valid.")) @@ -142,7 +134,29 @@ public ZRecipeConfiguration(Plugin plugin, String name, String path, YamlConfigu } - this.amount = configuration.getInt(path + "result-amount", 1); + if(!configuration.contains(path + "result.item")) { + throw new IllegalArgumentException("The recipe " + name + " doesn't have a result."); + } + String strItem = configuration.getString(path + "result.item"); + if (strItem == null) { + throw new IllegalArgumentException("The recipe " + name + " doesn't have a result."); + } + String[] resultParts = strItem.split(":"); + if(resultParts.length == 1) { + this.result = this.getItemStack(resultParts[0]); + } else { + this.result = switch (resultParts[0]) { + case "material" -> new ItemStack(this.getMaterial(resultParts[1])); + case "item", "base64" -> this.getItemStack(resultParts[1]); + default -> Hook.HOOKS.stream() + .filter(Hook::isEnable) + .filter(hook -> hook.getPluginName().equalsIgnoreCase(resultParts[0])) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("The result " + strItem + " isn't valid.")) + .getItemStack(resultParts[1]); + }; + } + this.amount = configuration.getInt(path + "result.amount", 1); this.cookingTime = configuration.getInt(path + "cooking-time", 0); @@ -151,7 +165,6 @@ public ZRecipeConfiguration(Plugin plugin, String name, String path, YamlConfigu /** * This method is used to get Tag from the string. - * * @param data the data to get the tag. * @return the tag. */ @@ -161,26 +174,41 @@ private Tag getTag(String data) { /** * This method is used to check if the ingredient is strict. - * * @param ingredient the ingredient to check. */ - private boolean isStrict(Map ingredient) { + private boolean isStrict(Map ingredient) { return ingredient.containsKey("strict") && (boolean) ingredient.get("strict"); } /** * This method is used to get the itemstack from base64 string - * * @param base64itemstack the base64 item stack. * @return the item stack. */ private ItemStack getItemStack(String base64itemstack) { - return Base64.decodeItem(base64itemstack); + try { + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Base64.getDecoder().decode(base64itemstack)); + GZIPInputStream gzipInputStream = new GZIPInputStream(byteArrayInputStream); + ObjectInputStream objectInputStream = new BukkitObjectInputStream(gzipInputStream); + Object deserialized = objectInputStream.readObject(); + objectInputStream.close(); + + if (!(deserialized instanceof ItemStack)) { + throw new IllegalArgumentException("The deserialized object is not an ItemStack."); + } + + return (ItemStack) deserialized; + } catch (IOException exception) { + throw new IllegalArgumentException("The itemstack " + base64itemstack + " is not a valid base64 or corrupted: " + exception.getMessage()); + } catch (ClassNotFoundException exception) { + throw new IllegalArgumentException("The itemstack " + base64itemstack + " contains an unknown class: " + exception.getMessage()); + } catch (IllegalArgumentException exception) { + throw new IllegalArgumentException("The itemstack " + base64itemstack + " is not valid: " + exception.getMessage()); + } } /** * This method is used to get the material from the string. - * * @param material the material string. * @return the material. */ @@ -194,22 +222,78 @@ private Material getMaterial(String material) { /** * This method is used to check if the category is valid. - * * @param category the group to check. * @return true if the category is valid. */ - private boolean checkGategory(String category) { - category = category.toUpperCase(); - try { - CookingBookCategory.valueOf(category); - } catch (IllegalArgumentException ignored) { - try { - CraftingBookCategory.valueOf(category); - } catch (IllegalArgumentException ignored_2) { - return false; + private boolean checkCategory(@NotNull String category) { + if(category.isEmpty()) { + return true; + } + + String upperCategory = category.toUpperCase(); + + for(CookingBookCategory cookingCategory : CookingBookCategory.values()) { + if(cookingCategory.name().equals(upperCategory)) { + return true; + } + } + + for(CraftingBookCategory craftingCategory : CraftingBookCategory.values()) { + if(craftingCategory.name().equals(upperCategory)) { + return true; + } + } + + return false; + } + + /** + * This method is used to validate the pattern. + * It checks if the pattern is valid for a shaped recipe. + */ + private void validatePattern() { + if (this.pattern == null || this.pattern.length == 0) { + throw new IllegalArgumentException("The recipe " + name + " has an empty pattern."); + } + + // Validate pattern size (max 3 rows) + if (this.pattern.length > 3) { + throw new IllegalArgumentException("The recipe " + name + " has a pattern with more than 3 rows."); + } + + // Validate each row length (max 3 characters) and collect all characters + Set patternChars = new HashSet<>(); + for (int i = 0; i < this.pattern.length; i++) { + String row = this.pattern[i]; + if (row.length() > 3) { + throw new IllegalArgumentException("The recipe " + name + " has a pattern row '" + row + "' with more than 3 characters."); + } + if (row.isEmpty()) { + throw new IllegalArgumentException("The recipe " + name + " has an empty pattern row at index " + i + "."); + } + // Collect all non-space characters + for (char c : row.toCharArray()) { + if (c != ' ') { + patternChars.add(c); + } + } + } + + // Validate that all pattern characters will have corresponding ingredients + if (!patternChars.isEmpty()) { + Set ingredientSigns = new HashSet<>(); + for (Ingredient ingredient : ingredientList) { + if (ingredient.sign() != null) { + ingredientSigns.add(ingredient.sign()); + } + } + + for (Character patternChar : patternChars) { + if (!ingredientSigns.contains(patternChar)) { + throw new IllegalArgumentException("The recipe " + name + " has a pattern character '" + patternChar + "' that doesn't match any ingredient sign."); + } } } - return true; } /** diff --git a/API/src/main/java/fr/maxlego08/items/api/runes/Rune.java b/API/src/main/java/fr/maxlego08/items/api/runes/Rune.java index 03b47cf..32a1d68 100644 --- a/API/src/main/java/fr/maxlego08/items/api/runes/Rune.java +++ b/API/src/main/java/fr/maxlego08/items/api/runes/Rune.java @@ -8,20 +8,62 @@ public interface Rune { + /** + * Get the parent of the rune. + * + * @return the parent of the rune + */ String getParent(); + /** + * Get the name of the rune. + * + * @return the name of the rune + */ String getName(); + /** + * Get the display name of the rune. + * + * @return the display name of the rune + */ String getDisplayName(); + /** + * Get the type of the rune. + * + * @return the type of the rune + */ RuneType getType(); + /** + * Get the list of materials associated with the rune. + * + * @return the list of materials associated with the rune + */ List getMaterials(); + /** + * Get the list of tags associated with the rune. + * A tag is an object that contains a list of materials associated with the rune. + * + * @return the list of tags associated with the rune + */ List> getTags(); + /** + * Check if a material is allowed by the rune. + * + * @param material the material to check + * @return true if the material is allowed, false otherwise + */ boolean isAllowed(Material material); + /** + * Gets the configuration of the rune. + * + * @return the configuration of the rune + */ T getConfiguration(); } diff --git a/API/src/main/java/fr/maxlego08/items/api/runes/RuneActivator.java b/API/src/main/java/fr/maxlego08/items/api/runes/RuneActivator.java index 03b8337..549b1d1 100644 --- a/API/src/main/java/fr/maxlego08/items/api/runes/RuneActivator.java +++ b/API/src/main/java/fr/maxlego08/items/api/runes/RuneActivator.java @@ -6,9 +6,23 @@ public interface RuneActivator { + /** + * Applies damage to an item stack. + * This method will apply damage to the given item stack, using the given amount of damage and the given living entity. + * If the item stack's durability is 0 after applying the damage, the item stack will be removed from the player's inventory. + * + * @param itemStack the item stack to apply the damage to + * @param damage the amount of damage to apply + * @param livingEntity the living entity that is applying the damage + */ default void applyDamageToItem(ItemStack itemStack, int damage, LivingEntity livingEntity) { itemStack.damage(damage, livingEntity); } + /** + * Returns the priority of this RuneActivator. + * + * @return the priority of this RuneActivator + */ int getPriority(); } diff --git a/API/src/main/java/fr/maxlego08/items/api/runes/RuneManager.java b/API/src/main/java/fr/maxlego08/items/api/runes/RuneManager.java index ad2ca96..6bf8692 100644 --- a/API/src/main/java/fr/maxlego08/items/api/runes/RuneManager.java +++ b/API/src/main/java/fr/maxlego08/items/api/runes/RuneManager.java @@ -16,35 +16,124 @@ public interface RuneManager { + /** + * Loads all the runes from the configuration files. + * This method is called when the plugin is enabled. + */ void loadRunes(); + /** + * Loads all the crafts from the configuration files that uses runes. + * This method is called when the plugin is enabled. + */ void loadCraftWithRunes(); + /** + * Loads a rune from a configuration file. + * This method is called when the plugin is enabled. + * + * @param file the file to load + */ void loadRune(File file); + /** + * Get a rune by its name. + * + * @param name the name of the rune to get + * @return the rune if found, otherwise an empty Optional + */ Optional getRune(String name); + /** + * Get all the runes. + * + * @return a list of all the runes + */ List getRunes(); + /** + * Gets all the runes that have the given type. + * + * @param runeType the type of the runes to get + * @return a list of all the runes that have the given type + */ List getRunes(RuneType runeType); + /** + * Gets all the runes from an item stack. + * + * @param itemStack the item stack to get the runes from + * @return an Optional containing a list of all the runes if found, otherwise an empty Optional + */ Optional> getRunes(ItemStack itemStack); + /** + * Applies a rune to an item stack of a player. + * + * @param player the player who owns the item stack + * @param runName the name of the rune to apply + */ void applyRune(Player player, String runName); + /** + * Applies a rune to an item stack. + * This method will throw a RuneException if the rune cannot be applied to the item stack. + * + * @param itemStack the item stack to apply the rune to + * @param rune the rune to apply + * @throws RuneException if the rune cannot be applied to the item stack + */ void applyRune(ItemStack itemStack, Rune rune) throws RuneException; + /** + * Gets the NamespacedKey used to store the runes in an item stack's metadata. + * + * @return the NamespacedKey used to store the runes in an item stack's metadata + */ NamespacedKey getKey(); + /** + * Gets the NamespacedKey used to store the rune that represents an item stack in its metadata. + * + * @return the NamespacedKey used to store the rune that represents an item stack in its metadata + */ NamespacedKey getRuneRepresentKey(); + /** + * Gets the PersistentDataType used to store a rune in an item stack's metadata. + * + * @return the PersistentDataType used to store a rune in an item stack's metadata + */ PersistentDataType getDataType(); + /** + * Deletes all the crafts associated with the runes. + * This method should be called when the plugin is disabled. + */ void deleteCrafts(); + /** + * Handles a player event. + * This method is called when a player event is fired. + * + * @param event the player event to handle + * @param the type of the player event + */ void onPlayerEvent(T event); + /** + * Gets all the applicators. + * + * @return a list of all the applicators + */ List getApplicators(); + /** + * Gets a map of all the runes to their associated recipes. + * This map contains all the recipes that use at least one rune. + * The key of the map is the rune and the value is a list of all the recipes that uses the rune. + * + * @return a map of all the runes to their associated recipes + */ Map> getRecipesUseRunes(); } diff --git a/API/src/main/java/fr/maxlego08/items/api/runes/RuneTypes.java b/API/src/main/java/fr/maxlego08/items/api/runes/RuneTypes.java index 5190bf5..8dc185e 100644 --- a/API/src/main/java/fr/maxlego08/items/api/runes/RuneTypes.java +++ b/API/src/main/java/fr/maxlego08/items/api/runes/RuneTypes.java @@ -1,7 +1,17 @@ package fr.maxlego08.items.api.runes; import fr.maxlego08.items.api.ItemPlugin; -import fr.maxlego08.items.api.runes.configurations.*; +import fr.maxlego08.items.api.runes.configurations.EmptyConfiguration; +import fr.maxlego08.items.api.runes.configurations.RuneAttributeConfiguration; +import fr.maxlego08.items.api.runes.configurations.RuneConfiguration; +import fr.maxlego08.items.api.runes.configurations.RuneEnchantApplicatorConfiguration; +import fr.maxlego08.items.api.runes.configurations.RuneFarmingHoeConfiguration; +import fr.maxlego08.items.api.runes.configurations.RuneHammerConfiguration; +import fr.maxlego08.items.api.runes.configurations.RuneMoneyBoostConfiguration; +import fr.maxlego08.items.api.runes.configurations.RuneSellingConfiguration; +import fr.maxlego08.items.api.runes.configurations.RuneVeinMiningConfiguration; +import fr.maxlego08.items.api.runes.configurations.RuneXPBoostConfiguration; +import fr.maxlego08.items.api.runes.configurations.SlotChangeConfiguration; import org.bukkit.configuration.file.YamlConfiguration; import java.lang.reflect.InvocationTargetException; @@ -44,6 +54,8 @@ public List getIncompatibles() { }, SELL_STICK("SellStick", RuneSellingConfiguration.class), SLOT_CHANGE("SlotChange", SlotChangeConfiguration.class), + INFINITE_BUCKET("InfiniteBucket", EmptyConfiguration.class), + EMPTY("Empty", EmptyConfiguration.class), ; private final RuneActivator activator; diff --git a/API/src/main/java/fr/maxlego08/items/api/runes/handlers/BucketHandler.java b/API/src/main/java/fr/maxlego08/items/api/runes/handlers/BucketHandler.java new file mode 100644 index 0000000..aa2c06f --- /dev/null +++ b/API/src/main/java/fr/maxlego08/items/api/runes/handlers/BucketHandler.java @@ -0,0 +1,30 @@ +package fr.maxlego08.items.api.runes.handlers; + +import fr.maxlego08.items.api.ItemPlugin; +import fr.maxlego08.items.api.runes.configurations.RuneConfiguration; +import org.bukkit.event.player.PlayerBucketEmptyEvent; +import org.bukkit.event.player.PlayerBucketFillEvent; + +public interface BucketHandler { + + /** + * Called when a player empties a bucket (places water/lava). + * Return true to cancel the bucket emptying (making it infinite). + * + * @param plugin The item plugin instance + * @param event The bucket empty event + * @param runeConfiguration The rune configuration + */ + void onBucketEmpty(ItemPlugin plugin, PlayerBucketEmptyEvent event, T runeConfiguration); + + /** + * Called when a player fills a bucket (picks up water/lava). + * Return true to cancel the bucket filling (keeping it empty or keeping its current state). + * + * @param plugin The item plugin instance + * @param event The bucket fill event + * @param runeConfiguration The rune configuration + */ + void onBucketFill(ItemPlugin plugin, PlayerBucketFillEvent event, T runeConfiguration); + +} \ No newline at end of file diff --git a/API/src/main/java/fr/maxlego08/items/api/utils/Helper.java b/API/src/main/java/fr/maxlego08/items/api/utils/Helper.java index cf541fc..1b0c4c0 100644 --- a/API/src/main/java/fr/maxlego08/items/api/utils/Helper.java +++ b/API/src/main/java/fr/maxlego08/items/api/utils/Helper.java @@ -1,40 +1,7 @@ package fr.maxlego08.items.api.utils; -import fr.maxlego08.items.api.Item; -import fr.maxlego08.items.api.ItemPlugin; -import org.bukkit.Material; -import org.bukkit.inventory.RecipeChoice; - public class Helper { - public static RecipeChoice getRecipeChoiceFromString(ItemPlugin plugin, String ingredient, String fileName) { - String[] ingredientArray = ingredient.split("\\|"); - switch (ingredientArray[0].trim()) { - case "item" -> { - String[] data = ingredientArray[1].trim().split(":"); - if (data[0].equalsIgnoreCase("minecraft")) { - return new RecipeChoice.MaterialChoice(Material.valueOf(data[1].trim().toUpperCase())); - } else if (data[0].equalsIgnoreCase("zitems")) { - Item ingredientItem = plugin.getItemManager().getItem(data[1].trim()).orElseThrow(() -> new IllegalArgumentException("Invalid item name")); - return new RecipeChoice.MaterialChoice(ingredientItem.build(null, 1).getType()); - } else if (data.length == 1) { - try { - return new RecipeChoice.MaterialChoice(Material.valueOf(data[0].trim().toUpperCase())); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("Invalid material for ingredient " + ingredient + " in " + fileName); - } - } else { - throw new IllegalArgumentException("Invalid item type for ingredient " + ingredient + " in " + fileName); - } - } - case "tag" -> { - return new RecipeChoice.MaterialChoice(TagRegistry.getTag(ingredientArray[1].trim().toUpperCase())); - } - default -> - throw new IllegalArgumentException("Invalid ingredient type for ingredient " + ingredient + " in " + fileName); - } - } - public static int between(int value, int min, int max) { return value > max ? max : Math.max(value, min); } diff --git a/API/src/main/java/fr/maxlego08/items/api/utils/Plugins.java b/API/src/main/java/fr/maxlego08/items/api/utils/Plugins.java index 88641c1..9c7d706 100644 --- a/API/src/main/java/fr/maxlego08/items/api/utils/Plugins.java +++ b/API/src/main/java/fr/maxlego08/items/api/utils/Plugins.java @@ -4,17 +4,9 @@ public enum Plugins { - VAULT("Vault"), - ESSENTIALS("Essentials"), - HEADDATABASE("HeadDatabase"), - PLACEHOLDER("PlaceholderAPI"), - CITIZENS("Citizens"), - TRANSLATIONAPI("TranslationAPI"), - ZTRANSLATOR("zTranslator"), WORLDGUARD("WorldGuard"), JOBS("Jobs"), ZJOBS("zJobs"), - ZMENU("zMenu"), ZESSENTIALS("zEssentials"), ITEMSADDER("ItemsAdder"), ZSHOP("zShop"), diff --git a/API/src/main/java/fr/maxlego08/items/api/utils/TagRegistry.java b/API/src/main/java/fr/maxlego08/items/api/utils/TagRegistry.java index 34c7594..3cc23dd 100644 --- a/API/src/main/java/fr/maxlego08/items/api/utils/TagRegistry.java +++ b/API/src/main/java/fr/maxlego08/items/api/utils/TagRegistry.java @@ -6,10 +6,13 @@ import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; public class TagRegistry { private static final Map> tagMap = new HashMap<>(); + private static final Logger LOGGER = Logger.getLogger(TagRegistry.class.getName()); static { for (Field field : Tag.class.getDeclaredFields()) { @@ -20,7 +23,7 @@ public class TagRegistry { register(field.getName(), (Tag) field.get(null)); } } catch (Exception exception) { - exception.printStackTrace(); + LOGGER.log(Level.SEVERE, "Failed to register tag from field: " + field.getName(), exception); } } } diff --git a/build.gradle.kts b/build.gradle.kts index b80caeb..e2c2df3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,3 @@ -import org.gradle.kotlin.dsl.invoke - plugins { `java-library` id("com.gradleup.shadow") version "9.0.0-beta11" @@ -7,7 +5,9 @@ plugins { } group = "fr.maxlego08.items" -version = "1.0.0.0" +version = "1.0.0" + +apply("gradle/copy-build.gradle") extra.set("targetFolder", file("target/")) extra.set("apiFolder", file("target-api/")) @@ -41,8 +41,6 @@ allprojects { } tasks.shadowJar { - - archiveBaseName.set("zItems") archiveAppendix.set(if (project.path == ":") "" else project.name) archiveClassifier.set("") @@ -50,6 +48,7 @@ allprojects { tasks.compileJava { options.encoding = "UTF-8" + options.release = 21 } tasks.javadoc { @@ -59,13 +58,13 @@ allprojects { } dependencies { - compileOnly("io.papermc.paper:paper-api:1.21.7-R0.1-SNAPSHOT") - compileOnly("com.mojang:authlib:1.5.26") + compileOnly("io.papermc.paper:paper-api:1.21.8-R0.1-SNAPSHOT") compileOnly("me.clip:placeholderapi:2.11.6") - compileOnly(files("libs/zMenu-1.1.0.1.jar")) - compileOnly(files("libs/API-1.0.2.6-all.jar")) - implementation("com.github.Traqueur-dev:RecipesAPI:1.4.4") + compileOnly("fr.maxlego08.menu:zmenu-api:1.1.0.4") + compileOnly("fr.maxlego08.essentials:zessentials-api:1.0.2.7") + compileOnly(files(rootProject.files("libs/zMenu-1.1.0.4.jar"))) + implementation("com.github.Traqueur-dev:RecipesAPI:3.0.0") implementation("com.jeff-media:armor-equip-event:1.0.3") } } @@ -75,9 +74,11 @@ repositories { } dependencies { - api(projects.api) - api(projects.hooks) + api(project(":API")) + rootProject.subprojects.filter { it.path.startsWith(":Hooks:") }.forEach { subproject -> + api(project(subproject.path)) + } } tasks { @@ -98,7 +99,7 @@ tasks { } compileJava { - options.release = 21 + // Configuration already set in allprojects block } processResources { diff --git a/gradle/copy-build.gradle b/gradle/copy-build.gradle new file mode 100644 index 0000000..d2c3a15 --- /dev/null +++ b/gradle/copy-build.gradle @@ -0,0 +1,52 @@ +buildscript { + dependencies { + classpath 'org.yaml:snakeyaml:2.2' + } + repositories { + mavenCentral() + } +} + +import org.yaml.snakeyaml.Yaml + +def localYamlFile = file("destinations.yml") +def copyTargets = [] + +if (localYamlFile.exists()) { + def yaml = new Yaml() + def data = yaml.load(localYamlFile.newInputStream()) + copyTargets = data["copy-dest"] ?: [] +} + +tasks.register("copyJarToLocalDirs") { + dependsOn tasks.named("shadowJar") + + doLast { + def sourceDir = rootProject.hasProperty('targetFolder') ? + rootProject.targetFolder : + "${project.buildDir}/libs" + + println "📦 Copying files from: $sourceDir" + + copyTargets.each { targetPath -> + def target = file(targetPath) + target.mkdirs() + + copy { + from sourceDir + into target + include '**/*' + } + + println " → $targetPath" + } + + if (copyTargets.isEmpty()) { + println "⚠️ No copy destinations found in destinations.yml" + } + } +} + +tasks.named("build") { + finalizedBy("copyJarToLocalDirs") +} diff --git a/gradlew b/gradlew old mode 100755 new mode 100644 diff --git a/libs/API-1.0.2.6-all.jar b/libs/API-1.0.2.6-all.jar deleted file mode 100644 index e3763da..0000000 Binary files a/libs/API-1.0.2.6-all.jar and /dev/null differ diff --git a/libs/zMenu-1.1.0.1.jar b/libs/zMenu-1.1.0.1.jar deleted file mode 100644 index d59682c..0000000 Binary files a/libs/zMenu-1.1.0.1.jar and /dev/null differ diff --git a/libs/zMenu-1.1.0.4.jar b/libs/zMenu-1.1.0.4.jar new file mode 100644 index 0000000..bc1cd72 Binary files /dev/null and b/libs/zMenu-1.1.0.4.jar differ diff --git a/pom.xml b/pom.xml deleted file mode 100644 index e13aaf6..0000000 --- a/pom.xml +++ /dev/null @@ -1,163 +0,0 @@ - - 4.0.0 - fr.maxlego08.items - zItems - 1.0.0 - - UTF-8 - 21 - 21 - - - src - - - maven-compiler-plugin - 3.8.1 - - 21 - 21 - - - - org.apache.maven.plugins - maven-shade-plugin - 3.4.1 - - - package - - shade - - - - - - - - - - jitpack.io - https://jitpack.io - - - spigot-repo - https://hub.spigotmc.org/nexus/content/repositories/snapshots/ - - - placeholderapi - https://repo.extendedclip.com/content/repositories/placeholderapi/ - - - minecraft-repo - https://libraries.minecraft.net/ - - - papermc - https://repo.papermc.io/repository/maven-public/ - - - sk89q-repo - https://maven.enginehub.org/repo/ - - - repository-local - file://${project.basedir}/libs - - - - - io.papermc.paper - paper-api - 1.21.5-R0.1-SNAPSHOT - provided - - - me.clip - placeholderapi - 2.11.1 - provided - - - com.mojang - authlib - 3.11.50 - provided - - - com.sk89q.worldguard - worldguard-bukkit - 7.0.9 - provided - - - com.sk89q.worldedit - worldedit-bukkit - 7.2.14 - provided - - - com.github.Zrips - Jobs - v5.2.2.3 - provided - - - com.github.Maxlego08 - zMenu-API - 1.0.3.7 - system - ${project.basedir}/libs/zMenu-1.1.0.0.jar - - - fr.maxlego08 - zjobs - 1.0 - system - ${project.basedir}/libs/zJobs-1.0.0.jar - - - com.github.Traqueur-dev - RecipesAPI - 1.4.4 - - - com.github.LoneDev6 - api-itemsadder - 3.6.1 - provided - - - com.github.brcdev-minecraft - shopgui-api - 3.0.0 - provided - - - fr.maxlego08 - zshop - 3.1.9 - system - ${project.basedir}/libs/zshop-3.2.0.jar - - - fr.maxlego08.essentials - ZEssentials - 1.0.1.9 - system - ${project.basedir}/libs/zEssentialsAPI.jar - - - com.github.Gypopo - EconomyShopGUI-API - 1.7.2 - provided - - - com.jeff-media - armor-equip-event - 1.0.3 - - - \ No newline at end of file diff --git a/src/main/java/fr/maxlego08/items/ItemsPlugin.java b/src/main/java/fr/maxlego08/items/ItemsPlugin.java index c4a26a1..c484b66 100644 --- a/src/main/java/fr/maxlego08/items/ItemsPlugin.java +++ b/src/main/java/fr/maxlego08/items/ItemsPlugin.java @@ -10,24 +10,22 @@ import fr.maxlego08.items.api.hook.BlockAccess; import fr.maxlego08.items.api.hook.HookManager; import fr.maxlego08.items.api.hook.Hooks; -import fr.maxlego08.items.buttons.ConfirmButton; -import fr.maxlego08.items.buttons.ItemFilesButton; -import fr.maxlego08.items.hooks.*; -import fr.maxlego08.items.inventories.ApplicatorMenu; -import fr.maxlego08.items.buttons.applicator.ApplicatorBaseInputButton; -import fr.maxlego08.items.buttons.applicator.ApplicatorExtraInputButton; -import fr.maxlego08.items.buttons.applicator.ApplicatorInputButton; -import fr.maxlego08.items.buttons.applicator.ApplicatorOutputButton; -import fr.maxlego08.items.buttons.applicator.ApplicatorRuneInputButton; -import fr.maxlego08.items.buttons.ItemsButton; import fr.maxlego08.items.api.recipes.ZItemHook; import fr.maxlego08.items.api.runes.RuneManager; +import fr.maxlego08.items.api.utils.Plugins; import fr.maxlego08.items.api.utils.TrimHelper; +import fr.maxlego08.items.buttons.ConfirmButton; +import fr.maxlego08.items.buttons.ItemFilesButton; +import fr.maxlego08.items.buttons.ItemsButton; +import fr.maxlego08.items.buttons.applicator.*; import fr.maxlego08.items.command.commands.CommandItem; import fr.maxlego08.items.components.PaperComponent; import fr.maxlego08.items.components.SpigotComponent; import fr.maxlego08.items.enchantments.DisableEnchantsListener; import fr.maxlego08.items.enchantments.ZEnchantments; +import fr.maxlego08.items.hooks.*; +import fr.maxlego08.items.inventories.ApplicatorMenu; +import fr.maxlego08.items.listener.AnvilRuneFusionListener; import fr.maxlego08.items.listener.CommandsListener; import fr.maxlego08.items.listener.GrindstoneListener; import fr.maxlego08.items.listener.SmithingTableListener; @@ -39,11 +37,9 @@ import fr.maxlego08.items.save.MessageLoader; import fr.maxlego08.items.zcore.ZPlugin; import fr.maxlego08.items.zcore.utils.builder.CooldownBuilder; -import fr.maxlego08.items.api.utils.Plugins; import fr.maxlego08.menu.api.ButtonManager; import fr.maxlego08.menu.api.InventoryManager; import fr.maxlego08.menu.api.loader.NoneLoader; -import fr.maxlego08.menu.hooks.folialib.impl.PlatformScheduler; import fr.traqueur.recipes.api.RecipesAPI; import fr.traqueur.recipes.api.hook.Hook; import org.bukkit.Location; @@ -65,7 +61,6 @@ public class ItemsPlugin extends ZPlugin implements ItemPlugin { private InventoryManager inventoryManager; private ItemComponent itemComponent; private RuneListener runeListener; - private PlatformScheduler scheduler; private RecipesAPI recipesAPI; private GlobalConfiguration globalConfiguration; private CommandsListener commandsListener; @@ -82,8 +77,6 @@ public void onEnable() { var buttonManager = this.getProvider(ButtonManager.class); this.inventoryManager = this.getProvider(InventoryManager.class); - this.scheduler = inventoryManager.getScheduler(); - buttonManager.unregisters(this); buttonManager.register(new NoneLoader(this, ItemsButton.class, "ZITEMS_ITEMS")); buttonManager.register(new NoneLoader(this, ConfirmButton.class, "ZITEMS_CONFIRM")); @@ -109,6 +102,7 @@ public void onEnable() { this.addListener(new DisableEnchantsListener(this.itemManager)); this.addListener(new GrindstoneListener(this.itemManager)); this.addListener(new SmithingTableListener(this.itemManager, this.runeManager)); + this.addListener(new AnvilRuneFusionListener(this, this.runeManager)); this.addListener(new SpawnerListener()); this.addListener(this.commandsListener = new CommandsListener(this)); @@ -141,7 +135,6 @@ public void onEnable() { new Hooks(Plugins.JOBS, new JobsHook(this.runeManager)), new Hooks(Plugins.ZJOBS, new ZJobsHook(this.runeManager)), new Hooks(Plugins.ITEMSADDER, new ItemsAdderHook(this)) - // ToDo, add more hook )); Stream.of(ShopHooks.values()).forEach(shopHooks -> hooksList.add(new Hooks(shopHooks.getPlugin(), shopHooks))); @@ -227,11 +220,6 @@ public Item createItem(String name, ItemConfiguration itemConfiguration) { return new ZItem(this, name, itemConfiguration); } - @Override - public PlatformScheduler getScheduler() { - return scheduler; - } - @Override public RecipesAPI getRecipesAPI() { return this.recipesAPI; @@ -247,10 +235,6 @@ public RuneManager getRuneManager() { return runeManager; } - public RuneListener getRuneListener() { - return runeListener; - } - public void info(String string) { if (getConfig().getBoolean("enable-info", false)) { getLogger().info(string); diff --git a/src/main/java/fr/maxlego08/items/ZItem.java b/src/main/java/fr/maxlego08/items/ZItem.java index d4dab1d..f1dd3c3 100644 --- a/src/main/java/fr/maxlego08/items/ZItem.java +++ b/src/main/java/fr/maxlego08/items/ZItem.java @@ -9,23 +9,20 @@ import fr.maxlego08.items.api.runes.Rune; import fr.maxlego08.items.api.runes.exceptions.RuneException; import fr.maxlego08.items.zcore.utils.ZUtils; -import io.papermc.paper.datacomponent.item.Equippable; import org.bukkit.NamespacedKey; import org.bukkit.attribute.AttributeModifier; import org.bukkit.entity.Player; -import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ArmorMeta; import org.bukkit.inventory.meta.Damageable; import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.inventory.meta.LeatherArmorMeta; import org.bukkit.inventory.meta.Repairable; import org.bukkit.inventory.meta.components.EquippableComponent; import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataType; import java.util.UUID; +import java.util.logging.Level; public class ZItem extends ZUtils implements Item { @@ -158,7 +155,7 @@ public ItemStack build(Player player, int amount) { try { this.plugin.getRuneManager().applyRune(itemStack, rune); } catch (RuneException exception) { - exception.printStackTrace(); + plugin.getLogger().log(Level.SEVERE, "Failed to apply rune " + rune.getName() + " to item " + this.name, exception); } } diff --git a/src/main/java/fr/maxlego08/items/ZItemManager.java b/src/main/java/fr/maxlego08/items/ZItemManager.java index 0121029..f13c24c 100644 --- a/src/main/java/fr/maxlego08/items/ZItemManager.java +++ b/src/main/java/fr/maxlego08/items/ZItemManager.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.logging.Level; import java.util.stream.Stream; public class ZItemManager extends ZUtils implements ItemManager { @@ -36,7 +37,6 @@ public void loadItems() { File folder = new File(plugin.getDataFolder(), "items"); if (!folder.exists()) { if (folder.mkdirs()) { - // this.plugin.saveResource("items/example.yml", false); this.plugin.saveResource("items/armor-trim.yml", false); this.plugin.saveResource("items/custom_seed.yml", false); this.plugin.saveResource("items/empty_item.yml", false); @@ -64,7 +64,7 @@ public void loadItems() { try (Stream stream = Files.walk(folder.toPath())) { stream.skip(1).map(Path::toFile).filter(File::isFile).filter(e -> e.getName().endsWith(".yml")).forEach(this::loadItem); } catch (IOException exception) { - exception.printStackTrace(); + plugin.getLogger().log(Level.SEVERE, "Failed to load items from folder: " + folder.getPath(), exception); } @@ -92,8 +92,7 @@ public void loadItem(File file) { plugin.info("Loaded item " + file.getPath()); } catch (Exception exception) { - plugin.getLogger().severe("Impossible to load the item " + file.getPath()); - exception.printStackTrace(); + plugin.getLogger().log(Level.SEVERE, "Failed to load item from file: " + file.getPath(), exception); } } diff --git a/src/main/java/fr/maxlego08/items/buttons/ItemFilesButton.java b/src/main/java/fr/maxlego08/items/buttons/ItemFilesButton.java index 7bb8c94..92c79e8 100644 --- a/src/main/java/fr/maxlego08/items/buttons/ItemFilesButton.java +++ b/src/main/java/fr/maxlego08/items/buttons/ItemFilesButton.java @@ -1,7 +1,7 @@ package fr.maxlego08.items.buttons; import fr.maxlego08.items.ItemsPlugin; -import fr.maxlego08.items.api.CloneUtils; +import fr.maxlego08.items.utils.CloneUtils; import fr.maxlego08.items.api.Item; import fr.maxlego08.items.api.utils.ItemFile; import fr.maxlego08.menu.api.button.PaginateButton; diff --git a/src/main/java/fr/maxlego08/items/buttons/ItemsButton.java b/src/main/java/fr/maxlego08/items/buttons/ItemsButton.java index 708eaba..7ca0000 100644 --- a/src/main/java/fr/maxlego08/items/buttons/ItemsButton.java +++ b/src/main/java/fr/maxlego08/items/buttons/ItemsButton.java @@ -1,11 +1,11 @@ package fr.maxlego08.items.buttons; import fr.maxlego08.items.ItemsPlugin; -import fr.maxlego08.items.api.CloneUtils; +import fr.maxlego08.items.utils.CloneUtils; import fr.maxlego08.items.api.Item; -import fr.maxlego08.items.zcore.utils.inventory.Pagination; import fr.maxlego08.menu.api.button.PaginateButton; import fr.maxlego08.menu.api.engine.InventoryEngine; +import fr.maxlego08.menu.api.engine.Pagination; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.Plugin; diff --git a/src/main/java/fr/maxlego08/items/buttons/applicator/ApplicatorButton.java b/src/main/java/fr/maxlego08/items/buttons/applicator/ApplicatorButton.java index c1e2470..a86979c 100644 --- a/src/main/java/fr/maxlego08/items/buttons/applicator/ApplicatorButton.java +++ b/src/main/java/fr/maxlego08/items/buttons/applicator/ApplicatorButton.java @@ -1,6 +1,6 @@ package fr.maxlego08.items.buttons.applicator; -import fr.maxlego08.items.api.CloneUtils; +import fr.maxlego08.items.utils.CloneUtils; import fr.maxlego08.menu.api.button.Button; import fr.maxlego08.menu.api.engine.InventoryEngine; import org.bukkit.Material; diff --git a/src/main/java/fr/maxlego08/items/buttons/applicator/ApplicatorOutputButton.java b/src/main/java/fr/maxlego08/items/buttons/applicator/ApplicatorOutputButton.java index 5d6b8cc..a80bee2 100644 --- a/src/main/java/fr/maxlego08/items/buttons/applicator/ApplicatorOutputButton.java +++ b/src/main/java/fr/maxlego08/items/buttons/applicator/ApplicatorOutputButton.java @@ -1,6 +1,6 @@ package fr.maxlego08.items.buttons.applicator; -import fr.maxlego08.items.api.CloneUtils; +import fr.maxlego08.items.utils.CloneUtils; import fr.maxlego08.items.api.ItemPlugin; import fr.maxlego08.items.api.runes.Rune; import fr.maxlego08.items.api.runes.applicators.Applicator; diff --git a/src/main/java/fr/maxlego08/items/command/CommandManager.java b/src/main/java/fr/maxlego08/items/command/CommandManager.java index 4c73e33..86f2a54 100644 --- a/src/main/java/fr/maxlego08/items/command/CommandManager.java +++ b/src/main/java/fr/maxlego08/items/command/CommandManager.java @@ -15,6 +15,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.logging.Level; public class CommandManager extends ZUtils implements CommandExecutor, TabCompleter { @@ -157,7 +158,7 @@ private CommandType processRequirements(VCommand command, CommandSender sender, if (command.getPermission() == null || hasPermission(sender, command.getPermission())) { if (command.runAsync) { - super.runAsync(this.plugin, () -> { + this.runAsync(this.plugin, () -> { CommandType returnType = command.prePerform(this.plugin, sender, strings); if (returnType == CommandType.SYNTAX_ERROR) { message(sender, Message.COMMAND_SYNTAX_ERROR, "%syntax%", command.getSyntax()); @@ -287,7 +288,7 @@ public void registerCommand(Plugin plugin, String string, VCommand vCommand, Lis Logger.info("Unable to add the command " + vCommand.getSyntax()); } } catch (Exception exception) { - exception.printStackTrace(); + plugin.getLogger().log(Level.SEVERE, "Failed to register command: " + vCommand.getSyntax(), exception); } } diff --git a/src/main/java/fr/maxlego08/items/command/VCommand.java b/src/main/java/fr/maxlego08/items/command/VCommand.java index ef24bd6..d97bcce 100644 --- a/src/main/java/fr/maxlego08/items/command/VCommand.java +++ b/src/main/java/fr/maxlego08/items/command/VCommand.java @@ -12,6 +12,7 @@ import org.bukkit.entity.Player; import java.util.*; +import java.util.logging.Level; /** * Abstract class representing a command in the plugin. @@ -525,7 +526,7 @@ public CommandType prePerform(ItemsPlugin plugin, CommandSender commandSender, S return perform(plugin); } catch (Exception e) { if (Config.enableDebug) - e.printStackTrace(); + plugin.getLogger().log(Level.SEVERE, "Error executing command " + this.getFirst(), e); return CommandType.SYNTAX_ERROR; } } diff --git a/src/main/java/fr/maxlego08/items/command/commands/CommandItem.java b/src/main/java/fr/maxlego08/items/command/commands/CommandItem.java index 1f34388..4b3638a 100644 --- a/src/main/java/fr/maxlego08/items/command/commands/CommandItem.java +++ b/src/main/java/fr/maxlego08/items/command/commands/CommandItem.java @@ -1,11 +1,11 @@ package fr.maxlego08.items.command.commands; import fr.maxlego08.items.ItemsPlugin; +import fr.maxlego08.items.api.utils.Plugins; import fr.maxlego08.items.command.VCommand; import fr.maxlego08.items.command.commands.edit.CommandItemEdit; import fr.maxlego08.items.zcore.enums.Permission; import fr.maxlego08.items.zcore.utils.commands.CommandType; -import fr.maxlego08.items.api.utils.Plugins; public class CommandItem extends VCommand { diff --git a/src/main/java/fr/maxlego08/items/command/commands/CommandItemGui.java b/src/main/java/fr/maxlego08/items/command/commands/CommandItemGui.java index 7f27e67..0e90525 100644 --- a/src/main/java/fr/maxlego08/items/command/commands/CommandItemGui.java +++ b/src/main/java/fr/maxlego08/items/command/commands/CommandItemGui.java @@ -5,7 +5,6 @@ import fr.maxlego08.items.zcore.enums.Message; import fr.maxlego08.items.zcore.enums.Permission; import fr.maxlego08.items.zcore.utils.commands.CommandType; -import fr.maxlego08.menu.api.InventoryManager; public class CommandItemGui extends VCommand { diff --git a/src/main/java/fr/maxlego08/items/command/commands/CommandItemViewRunes.java b/src/main/java/fr/maxlego08/items/command/commands/CommandItemViewRunes.java index 19d67e1..1bd996d 100644 --- a/src/main/java/fr/maxlego08/items/command/commands/CommandItemViewRunes.java +++ b/src/main/java/fr/maxlego08/items/command/commands/CommandItemViewRunes.java @@ -23,16 +23,14 @@ protected CommandType perform(ItemsPlugin plugin) { var optRunes = plugin.getRuneManager().getRunes(this.player.getInventory().getItemInMainHand()); if(optRunes.isPresent()) { List runeLore = Message.RUNE_LORE.getMessages(); - //remove first empty line runeLore.removeFirst(); - - List formattedLore = new ArrayList<>(); - - runeLore.forEach(line -> formattedLore.add(color(line))); + List formattedLore = new ArrayList<>(runeLore); for (Rune rune : optRunes.get()) { - formattedLore.add(color(getMessage(Message.RUNE_LINE, "%rune%", rune.getDisplayName()))); + formattedLore.add(getMessage(Message.RUNE_LINE, "%rune%", rune.getDisplayName())); + } + for (String s : formattedLore) { + plugin.getItemComponent().sendMessage(this.player, s); } - this.player.sendMessage(formattedLore.toArray(new String[0])); } return CommandType.SUCCESS; } diff --git a/src/main/java/fr/maxlego08/items/components/PaperComponent.java b/src/main/java/fr/maxlego08/items/components/PaperComponent.java index 24cbafb..c96f544 100644 --- a/src/main/java/fr/maxlego08/items/components/PaperComponent.java +++ b/src/main/java/fr/maxlego08/items/components/PaperComponent.java @@ -1,7 +1,9 @@ package fr.maxlego08.items.components; import fr.maxlego08.items.api.ItemComponent; +import fr.maxlego08.items.api.ItemPlugin; import fr.maxlego08.items.zcore.enums.Message; +import fr.maxlego08.items.zcore.utils.MessageUtils; import fr.maxlego08.items.zcore.utils.ZUtils; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.TextDecoration; @@ -20,59 +22,84 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -public class PaperComponent extends ZUtils implements ItemComponent { - - private final MiniMessage MINI_MESSAGE = MiniMessage.builder().tags(TagResolver.builder().resolver(StandardTags.defaults()).build()).build(); - private final Map COLORS_MAPPINGS = new HashMap<>(); - - public PaperComponent() { - this.COLORS_MAPPINGS.put("0", "black"); - this.COLORS_MAPPINGS.put("1", "dark_blue"); - this.COLORS_MAPPINGS.put("2", "dark_green"); - this.COLORS_MAPPINGS.put("3", "dark_aqua"); - this.COLORS_MAPPINGS.put("4", "dark_red"); - this.COLORS_MAPPINGS.put("5", "dark_purple"); - this.COLORS_MAPPINGS.put("6", "gold"); - this.COLORS_MAPPINGS.put("7", "gray"); - this.COLORS_MAPPINGS.put("8", "dark_gray"); - this.COLORS_MAPPINGS.put("9", "blue"); - this.COLORS_MAPPINGS.put("a", "green"); - this.COLORS_MAPPINGS.put("b", "aqua"); - this.COLORS_MAPPINGS.put("c", "red"); - this.COLORS_MAPPINGS.put("d", "light_purple"); - this.COLORS_MAPPINGS.put("e", "yellow"); - this.COLORS_MAPPINGS.put("f", "white"); - this.COLORS_MAPPINGS.put("k", "obfuscated"); - this.COLORS_MAPPINGS.put("l", "bold"); - this.COLORS_MAPPINGS.put("m", "strikethrough"); - this.COLORS_MAPPINGS.put("n", "underlined"); - this.COLORS_MAPPINGS.put("o", "italic"); - this.COLORS_MAPPINGS.put("r", "reset"); +public class PaperComponent implements ItemComponent { + + private static final MiniMessage MINI_MESSAGE = MiniMessage.builder().tags(TagResolver.builder().resolver(StandardTags.defaults()).build()).build(); + private static final Map COLORS_MAPPINGS = new HashMap<>(); + + static { + COLORS_MAPPINGS.put("0", "black"); + COLORS_MAPPINGS.put("1", "dark_blue"); + COLORS_MAPPINGS.put("2", "dark_green"); + COLORS_MAPPINGS.put("3", "dark_aqua"); + COLORS_MAPPINGS.put("4", "dark_red"); + COLORS_MAPPINGS.put("5", "dark_purple"); + COLORS_MAPPINGS.put("6", "gold"); + COLORS_MAPPINGS.put("7", "gray"); + COLORS_MAPPINGS.put("8", "dark_gray"); + COLORS_MAPPINGS.put("9", "blue"); + COLORS_MAPPINGS.put("a", "green"); + COLORS_MAPPINGS.put("b", "aqua"); + COLORS_MAPPINGS.put("c", "red"); + COLORS_MAPPINGS.put("d", "light_purple"); + COLORS_MAPPINGS.put("e", "yellow"); + COLORS_MAPPINGS.put("f", "white"); + COLORS_MAPPINGS.put("k", "obfuscated"); + COLORS_MAPPINGS.put("l", "bold"); + COLORS_MAPPINGS.put("m", "strikethrough"); + COLORS_MAPPINGS.put("n", "underlined"); + COLORS_MAPPINGS.put("o", "italic"); + COLORS_MAPPINGS.put("r", "reset"); } private String colorMiniMessage(String message) { - StringBuilder stringBuilder = new StringBuilder(); - - Pattern pattern = Pattern.compile("(?"); + // First, convert legacy color codes to MiniMessage format + for (Map.Entry entry : COLORS_MAPPINGS.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + message = message.replace("&" + key, "<" + value + ">"); + message = message.replace("§" + key, "<" + value + ">"); + message = message.replace("&" + key.toUpperCase(), "<" + value + ">"); + message = message.replace("§" + key.toUpperCase(), "<" + value + ">"); } - matcher.appendTail(stringBuilder); - String newMessage = stringBuilder.toString(); + // Then convert hex colors that are NOT already inside MiniMessage tags + // We parse character by character to avoid converting hex inside existing tags + StringBuilder result = new StringBuilder(); + int i = 0; + int length = message.length(); + + while (i < length) { + char currentChar = message.charAt(i); + + // If we encounter a '<', skip the entire tag to avoid modifying it + if (currentChar == '<') { + int closeIndex = message.indexOf('>', i); + if (closeIndex != -1) { + // Copy the entire tag as-is (including < and >) + result.append(message.substring(i, closeIndex + 1)); + i = closeIndex + 1; + continue; + } + } - for (Map.Entry entry : this.COLORS_MAPPINGS.entrySet()) { - String key = entry.getKey(); - String value = entry.getValue(); - newMessage = newMessage.replace("&" + key, "<" + value + ">"); - newMessage = newMessage.replace("§" + key, "<" + value + ">"); - newMessage = newMessage.replace("&" + key.toUpperCase(), "<" + value + ">"); - newMessage = newMessage.replace("§" + key.toUpperCase(), "<" + value + ">"); + // Check if we have a hex color code (#RRGGBB) + if (currentChar == '#' && i + 6 < length) { + String potentialHex = message.substring(i + 1, i + 7); + if (potentialHex.matches("[a-fA-F0-9]{6}")) { + // Valid hex color, wrap it in MiniMessage tags + result.append("<#").append(potentialHex).append(">"); + i += 7; + continue; + } + } + + // Regular character, just append it + result.append(currentChar); + i++; } - return newMessage; + return result.toString(); } private TextDecoration.State getState(String text) { @@ -127,7 +154,10 @@ public void setLoreIndex(ItemMeta itemMeta, int index, String line) { @Override public void sendItemLore(Player player, ItemMeta itemMeta) { List lore = itemMeta.lore(); - message(player, Message.COMMAND_ITEM_LORE); + + + player.sendMessage(getComponent(MessageUtils.getMessage(Message.COMMAND_ITEM_LORE))); + if (lore != null) { for (Component component : lore) { Component message = getComponent(Message.COMMAND_ITEM_LORE_LINE.getMessage()); @@ -141,5 +171,24 @@ public void sendMessage(CommandSender sender, String string) { sender.sendMessage(getComponent(string)); } + @Override + public void sendActionBar(Player player, String message) { + player.sendActionBar(getComponent(message)); + } + + @Override + public void sendTitle(Player player, String title, String subtitle, int fadeInTime, int showTime, int fadeOutTime) { + var titleComponent = getComponent(title); + var subtitleComponent = getComponent(subtitle); + player.showTitle(net.kyori.adventure.title.Title.title( + titleComponent, + subtitleComponent, + net.kyori.adventure.title.Title.Times.times( + java.time.Duration.ofMillis(fadeInTime), + java.time.Duration.ofMillis(showTime), + java.time.Duration.ofMillis(fadeOutTime) + ) + )); + } } diff --git a/src/main/java/fr/maxlego08/items/components/SpigotComponent.java b/src/main/java/fr/maxlego08/items/components/SpigotComponent.java index 6801dea..f7b86cf 100644 --- a/src/main/java/fr/maxlego08/items/components/SpigotComponent.java +++ b/src/main/java/fr/maxlego08/items/components/SpigotComponent.java @@ -1,15 +1,21 @@ package fr.maxlego08.items.components; +import fr.maxlego08.items.ItemsPlugin; import fr.maxlego08.items.api.ItemComponent; import fr.maxlego08.items.zcore.utils.ZUtils; +import fr.maxlego08.items.zcore.utils.nms.NmsVersion; import org.bukkit.block.sign.SignSide; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.plugin.java.JavaPlugin; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class SpigotComponent extends ZUtils implements ItemComponent { + @Override public void setItemName(ItemMeta itemMeta, String name) { itemMeta.setItemName(color(name)); @@ -46,4 +52,34 @@ public void sendItemLore(Player player, ItemMeta itemMeta) { public void sendMessage(CommandSender sender, String string) { sender.sendMessage(color(string)); } + + @Override + public void sendActionBar(Player player, String message) { + player.sendActionBar(color(message)); + } + + @Override + public void sendTitle(Player player, String title, String subtitle, int fadeInTime, int showTime, int fadeOutTime) { + player.sendTitle(color(title), color(subtitle), fadeInTime, showTime, fadeOutTime); + } + + private List color(List messages) { + return messages.stream().map(this::color).toList(); + } + + private String color(String message) { + if (message == null) { + return null; + } + if (NmsVersion.nmsVersion.isHexVersion()) { + Pattern pattern = Pattern.compile("#[a-fA-F0-9]{6}"); + Matcher matcher = pattern.matcher(message); + while (matcher.find()) { + String color = message.substring(matcher.start(), matcher.end()); + message = message.replace(color, String.valueOf(net.md_5.bungee.api.ChatColor.of(color))); + matcher = pattern.matcher(message); + } + } + return net.md_5.bungee.api.ChatColor.translateAlternateColorCodes('&', message); + } } diff --git a/src/main/java/fr/maxlego08/items/exceptions/InventoryAlreadyExistException.java b/src/main/java/fr/maxlego08/items/exceptions/InventoryAlreadyExistException.java index 9dfcf38..01ea637 100644 --- a/src/main/java/fr/maxlego08/items/exceptions/InventoryAlreadyExistException.java +++ b/src/main/java/fr/maxlego08/items/exceptions/InventoryAlreadyExistException.java @@ -4,28 +4,23 @@ public class InventoryAlreadyExistException extends Error { public InventoryAlreadyExistException() { super(); - // TODO Auto-generated constructor stub } public InventoryAlreadyExistException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); - // TODO Auto-generated constructor stub } public InventoryAlreadyExistException(String message, Throwable cause) { super(message, cause); - // TODO Auto-generated constructor stub } public InventoryAlreadyExistException(String message) { super(message); - // TODO Auto-generated constructor stub } public InventoryAlreadyExistException(Throwable cause) { super(cause); - // TODO Auto-generated constructor stub } /** diff --git a/src/main/java/fr/maxlego08/items/exceptions/InventoryOpenException.java b/src/main/java/fr/maxlego08/items/exceptions/InventoryOpenException.java index c7408c8..0a48977 100644 --- a/src/main/java/fr/maxlego08/items/exceptions/InventoryOpenException.java +++ b/src/main/java/fr/maxlego08/items/exceptions/InventoryOpenException.java @@ -6,28 +6,23 @@ public class InventoryOpenException extends Exception { public InventoryOpenException() { super(); - // TODO Auto-generated constructor stub } public InventoryOpenException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); - // TODO Auto-generated constructor stub } public InventoryOpenException(String message, Throwable cause) { super(message, cause); - // TODO Auto-generated constructor stub } public InventoryOpenException(String message) { super(message); - // TODO Auto-generated constructor stub } public InventoryOpenException(Throwable cause) { super(cause); - // TODO Auto-generated constructor stub } private static final long serialVersionUID = 1L; diff --git a/src/main/java/fr/maxlego08/items/exceptions/ItemCreateException.java b/src/main/java/fr/maxlego08/items/exceptions/ItemCreateException.java index 084c8af..2fa9fc2 100644 --- a/src/main/java/fr/maxlego08/items/exceptions/ItemCreateException.java +++ b/src/main/java/fr/maxlego08/items/exceptions/ItemCreateException.java @@ -9,27 +9,22 @@ public class ItemCreateException extends Error { public ItemCreateException() { super(); - // TODO Auto-generated constructor stub } public ItemCreateException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); - // TODO Auto-generated constructor stub } public ItemCreateException(String message, Throwable cause) { super(message, cause); - // TODO Auto-generated constructor stub } public ItemCreateException(String message) { super(message); - // TODO Auto-generated constructor stub } public ItemCreateException(Throwable cause) { super(cause); - // TODO Auto-generated constructor stub } diff --git a/src/main/java/fr/maxlego08/items/exceptions/ItemEnchantException.java b/src/main/java/fr/maxlego08/items/exceptions/ItemEnchantException.java index 60ca22b..130d7c1 100644 --- a/src/main/java/fr/maxlego08/items/exceptions/ItemEnchantException.java +++ b/src/main/java/fr/maxlego08/items/exceptions/ItemEnchantException.java @@ -14,48 +14,24 @@ public class ItemEnchantException extends Exception { */ private static final long serialVersionUID = 1L; - /** - * - */ public ItemEnchantException() { - // TODO Auto-generated constructor stub } - /** - * @param message - */ public ItemEnchantException(String message) { super(message); - // TODO Auto-generated constructor stub } - /** - * @param cause - */ public ItemEnchantException(Throwable cause) { super(cause); - // TODO Auto-generated constructor stub } - /** - * @param message - * @param cause - */ public ItemEnchantException(String message, Throwable cause) { super(message, cause); - // TODO Auto-generated constructor stub } - /** - * @param message - * @param cause - * @param enableSuppression - * @param writableStackTrace - */ public ItemEnchantException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); - // TODO Auto-generated constructor stub } } diff --git a/src/main/java/fr/maxlego08/items/exceptions/ItemFlagException.java b/src/main/java/fr/maxlego08/items/exceptions/ItemFlagException.java index 39b920c..4346f7f 100644 --- a/src/main/java/fr/maxlego08/items/exceptions/ItemFlagException.java +++ b/src/main/java/fr/maxlego08/items/exceptions/ItemFlagException.java @@ -8,27 +8,22 @@ public class ItemFlagException extends Exception { private static final long serialVersionUID = 1L; public ItemFlagException() { - // TODO Auto-generated constructor stub } public ItemFlagException(String message) { super(message); - // TODO Auto-generated constructor stub } public ItemFlagException(Throwable cause) { super(cause); - // TODO Auto-generated constructor stub } public ItemFlagException(String message, Throwable cause) { super(message, cause); - // TODO Auto-generated constructor stub } public ItemFlagException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); - // TODO Auto-generated constructor stub } } diff --git a/src/main/java/fr/maxlego08/items/exceptions/ListenerNullException.java b/src/main/java/fr/maxlego08/items/exceptions/ListenerNullException.java index b96cb0d..1b7653c 100644 --- a/src/main/java/fr/maxlego08/items/exceptions/ListenerNullException.java +++ b/src/main/java/fr/maxlego08/items/exceptions/ListenerNullException.java @@ -8,28 +8,23 @@ public class ListenerNullException extends Error { private static final long serialVersionUID = 1L; public ListenerNullException() { - // TODO Auto-generated constructor stub } public ListenerNullException(String message) { super(message); - // TODO Auto-generated constructor stub } public ListenerNullException(Throwable cause) { super(cause); - // TODO Auto-generated constructor stub } public ListenerNullException(String message, Throwable cause) { super(message, cause); - // TODO Auto-generated constructor stub } public ListenerNullException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); - // TODO Auto-generated constructor stub } } diff --git a/src/main/java/fr/maxlego08/items/hooks/ShopHooks.java b/src/main/java/fr/maxlego08/items/hooks/ShopHooks.java index ab91fc3..206a337 100644 --- a/src/main/java/fr/maxlego08/items/hooks/ShopHooks.java +++ b/src/main/java/fr/maxlego08/items/hooks/ShopHooks.java @@ -2,9 +2,6 @@ import fr.maxlego08.items.api.shop.ShopHook; import fr.maxlego08.items.api.shop.ShopProvider; -import fr.maxlego08.items.hooks.EconomyShopGUIProvider; -import fr.maxlego08.items.hooks.ShopGUIPlusProvider; -import fr.maxlego08.items.hooks.ZShopProvider; import fr.maxlego08.items.api.utils.Plugins; public enum ShopHooks implements ShopHook { diff --git a/src/main/java/fr/maxlego08/items/inventories/ApplicatorMenu.java b/src/main/java/fr/maxlego08/items/inventories/ApplicatorMenu.java index f8c9282..fcb74fc 100644 --- a/src/main/java/fr/maxlego08/items/inventories/ApplicatorMenu.java +++ b/src/main/java/fr/maxlego08/items/inventories/ApplicatorMenu.java @@ -1,6 +1,6 @@ package fr.maxlego08.items.inventories; -import fr.maxlego08.items.api.CloneUtils; +import fr.maxlego08.items.utils.CloneUtils; import fr.maxlego08.items.buttons.applicator.ApplicatorBaseInputButton; import fr.maxlego08.items.buttons.applicator.ApplicatorExtraInputButton; import fr.maxlego08.items.buttons.applicator.ApplicatorInputButton; diff --git a/src/main/java/fr/maxlego08/items/listener/AdapterListener.java b/src/main/java/fr/maxlego08/items/listener/AdapterListener.java deleted file mode 100644 index 805b37f..0000000 --- a/src/main/java/fr/maxlego08/items/listener/AdapterListener.java +++ /dev/null @@ -1,156 +0,0 @@ -package fr.maxlego08.items.listener; - -import fr.maxlego08.items.ItemsPlugin; -import fr.maxlego08.items.zcore.utils.ZUtils; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.entity.Projectile; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.block.BlockPlaceEvent; -import org.bukkit.event.entity.CreatureSpawnEvent; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.event.inventory.CraftItemEvent; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.inventory.InventoryCloseEvent; -import org.bukkit.event.inventory.InventoryDragEvent; -import org.bukkit.event.player.*; - -@SuppressWarnings("deprecation") -public class AdapterListener extends ZUtils implements Listener { - - private final ItemsPlugin plugin; - - public AdapterListener(ItemsPlugin plugin) { - this.plugin = plugin; - } - - @EventHandler - public void onConnect(PlayerJoinEvent event) { - this.plugin.getListenerAdapters().forEach(adapter -> adapter.onConnect(event, event.getPlayer())); - } - - @EventHandler - public void onQuit(PlayerQuitEvent event) { - this.plugin.getListenerAdapters().forEach(adapter -> adapter.onQuit(event, event.getPlayer())); - } - - - /*@EventHandler - public void onMove(PlayerMoveEvent event) { - this.plugin.getListenerAdapters().forEach(adapter -> adapter.onMove(event, event.getPlayer())); - if (event.getFrom().getBlockX() >> 1 == event.getTo().getBlockX() >> 1 && event.getFrom().getBlockZ() >> 1 == event.getTo().getBlockZ() >> 1 && event.getFrom().getWorld() == event.getTo().getWorld()) { - return; - } - this.plugin.getListenerAdapters().forEach(adapter -> adapter.onPlayerWalk(event, event.getPlayer(), 1)); - }*/ - - - @EventHandler - public void onInventoryClick(InventoryClickEvent event) { - this.plugin.getListenerAdapters() - .forEach(adapter -> adapter.onInventoryClick(event, (Player) event.getWhoClicked())); - } - - @EventHandler - public void onBlockBreak(BlockBreakEvent event) { - this.plugin.getListenerAdapters().forEach(adapter -> adapter.onBlockBreak(event, event.getPlayer())); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onBlockPlace(BlockPlaceEvent event) { - if (event.isCancelled()) return; - - this.plugin.getListenerAdapters().forEach(adapter -> adapter.onBlockPlace(event, event.getPlayer())); - } - - @EventHandler - public void onEntityDeath(EntityDeathEvent event) { - this.plugin.getListenerAdapters().forEach(adapter -> adapter.onEntityDeath(event, event.getEntity())); - } - - @EventHandler - public void onInteract(PlayerInteractEvent event) { - this.plugin.getListenerAdapters().forEach(adapter -> adapter.onInteract(event, event.getPlayer())); - } - - @EventHandler - public void onPlayerTalk(AsyncPlayerChatEvent event) { - this.plugin.getListenerAdapters().forEach(adapter -> adapter.onPlayerTalk(event, event.getMessage())); - } - - @EventHandler - public void onCraftItem(CraftItemEvent event) { - this.plugin.getListenerAdapters().forEach(adapter -> adapter.onCraftItem(event)); - } - - @EventHandler - public void onDrag(InventoryDragEvent event) { - this.plugin.getListenerAdapters() - .forEach(adapter -> adapter.onInventoryDrag(event, (Player) event.getWhoClicked())); - } - - @EventHandler - public void onClose(InventoryCloseEvent event) { - this.plugin.getListenerAdapters() - .forEach(adapter -> adapter.onInventoryClose(event, (Player) event.getPlayer())); - } - - @EventHandler - public void onCommand(PlayerCommandPreprocessEvent event) { - this.plugin.getListenerAdapters() - .forEach(adapter -> adapter.onCommand(event, event.getPlayer(), event.getMessage())); - } - - @EventHandler - public void onGamemodeChange(PlayerGameModeChangeEvent event) { - this.plugin.getListenerAdapters().forEach(adapter -> adapter.onGamemodeChange(event, event.getPlayer())); - } - - /* - * @EventHandler public void onDrop(PlayerDropItemEvent event) { - * this.plugin.getListenerAdapters().forEach(adapter -> - * adapter.onDrop(event, event.getPlayer())); if (!Config.useItemFallEvent) - * return; Item item = event.getItemDrop(); AtomicBoolean hasSendEvent = new - * AtomicBoolean(false); scheduleFix(100, (task, isActive) -> { if - * (!isActive) return; this.plugin.getListenerAdapters().forEach(adapter -> - * adapter.onItemMove(event, event.getPlayer(), item, item.getLocation(), - * item.getLocation().getBlock())); if (item.isOnGround() && - * !hasSendEvent.get()) { task.cancel(); hasSendEvent.set(true); - * this.plugin.getListenerAdapters().forEach( adapter -> - * adapter.onItemisOnGround(event, event.getPlayer(), item, - * item.getLocation())); } }); } - */ - - @EventHandler - public void onPick(PlayerPickupItemEvent event) { - this.plugin.getListenerAdapters().forEach(adapter -> adapter.onPickUp(event, event.getPlayer())); - } - - @EventHandler - public void onMobSpawn(CreatureSpawnEvent event) { - this.plugin.getListenerAdapters().forEach(adapter -> adapter.onMobSpawn(event)); - } - - @EventHandler - public void onDamage(EntityDamageByEntityEvent event) { - - if (event.getEntity() instanceof LivingEntity && event.getDamager() instanceof LivingEntity) { - this.plugin.getListenerAdapters().forEach(adapter -> adapter.onDamageByEntity(event, event.getCause(), - event.getDamage(), (LivingEntity) event.getDamager(), (LivingEntity) event.getEntity())); - } - - if (event.getEntity() instanceof Player && event.getDamager() instanceof Player) { - this.plugin.getListenerAdapters().forEach(adapter -> adapter.onPlayerDamagaByPlayer(event, event.getCause(), - event.getDamage(), (Player) event.getDamager(), (Player) event.getEntity())); - } - - if (event.getEntity() instanceof Player && event.getDamager() instanceof Projectile) { - this.plugin.getListenerAdapters().forEach(adapter -> adapter.onPlayerDamagaByArrow(event, event.getCause(), - event.getDamage(), (Projectile) event.getDamager(), (Player) event.getEntity())); - } - } -} diff --git a/src/main/java/fr/maxlego08/items/listener/AnvilRuneFusionListener.java b/src/main/java/fr/maxlego08/items/listener/AnvilRuneFusionListener.java new file mode 100644 index 0000000..aec3ad5 --- /dev/null +++ b/src/main/java/fr/maxlego08/items/listener/AnvilRuneFusionListener.java @@ -0,0 +1,263 @@ +package fr.maxlego08.items.listener; + +import fr.maxlego08.items.api.ItemPlugin; +import fr.maxlego08.items.api.runes.Rune; +import fr.maxlego08.items.api.runes.RuneManager; +import fr.maxlego08.items.api.runes.configurations.RuneEnchantApplicatorConfiguration; +import fr.maxlego08.items.api.runes.handlers.ItemApplicationHandler; +import fr.maxlego08.items.zcore.enums.Message; +import net.kyori.adventure.text.Component; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.PrepareAnvilEvent; +import org.bukkit.inventory.AnvilInventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.EnchantmentStorageMeta; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; + +import java.util.*; + +import static fr.maxlego08.items.zcore.utils.MessageUtils.getMessage; + +/** + * Listener responsible for handling rune fusion in anvils. + *

+ * This listener manages: + * - Rune incompatibility checks (blocks fusion if incompatible runes are present) + * - Rune merging from both items onto the result + * - Special handling for EnchantApplicator runes (treats enchantments as bonuses) + *

+ * Example: Pickaxe Effi 2 with EnchantApplicator +1 (displays Effi 3) fused with Effi 3 pickaxe + * → Result: Effi 3 base + EnchantApplicator +1 = Effi 4 displayed + */ +public class AnvilRuneFusionListener implements Listener { + + private final ItemPlugin plugin; + private final RuneManager runeManager; + + public AnvilRuneFusionListener(ItemPlugin plugin, RuneManager runeManager) { + this.plugin = plugin; + this.runeManager = runeManager; + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onAnvilPrepare(PrepareAnvilEvent event) { + AnvilInventory inventory = event.getInventory(); + ItemStack firstItem = inventory.getFirstItem(); + ItemStack secondItem = inventory.getSecondItem(); + + // If we don't have two items, do nothing + if (firstItem == null || secondItem == null || firstItem.getType().isAir() || secondItem.getType().isAir()) { + return; + } + + // Check if second item is an enchanted book + boolean isEnchantedBook = secondItem.getType().toString().equals("ENCHANTED_BOOK"); + + // Only handle items of the same type OR item + enchanted book (vanilla anvil logic) + if (firstItem.getType() != secondItem.getType() && !isEnchantedBook) { + return; + } + + // Get runes from both items + Optional> firstRunesOpt = runeManager.getRunes(firstItem); + Optional> secondRunesOpt = runeManager.getRunes(secondItem); + + // If neither item has runes and it's not an enchanted book fusion, do nothing + if (firstRunesOpt.isEmpty() && secondRunesOpt.isEmpty() && !isEnchantedBook) { + return; + } + + // If only the first item has runes and second is enchanted book, we need to handle this + if (firstRunesOpt.isPresent() && isEnchantedBook) { + // Continue to handle the fusion with runes + } else if (firstRunesOpt.isEmpty() && secondRunesOpt.isEmpty()) { + // No runes on either side, let vanilla handle it + return; + } + + List runes1 = firstRunesOpt.orElse(new ArrayList<>()); + List runes2 = secondRunesOpt.orElse(new ArrayList<>()); + + // Check for incompatibilities between the two items + for (Rune rune1 : runes1) { + for (Rune rune2 : runes2) { + if (areRunesIncompatible(rune1, rune2)) { + // Block the fusion + event.setResult(null); + return; + } + } + } + + // Start with the FIRST item as base (preserves all runes and their formatting) + ItemStack result = firstItem.clone(); + ItemMeta resultMeta = result.getItemMeta(); + if (resultMeta == null) { + return; + } + + // Calculate base enchantments (without EnchantApplicator bonuses) + Map baseEnchants1 = calculateBaseEnchantments(firstItem, runes1); + Map baseEnchants2 = calculateBaseEnchantments(secondItem, runes2); + + // Merge base enchantments (vanilla anvil logic) + Map mergedEnchants = mergeEnchantments(baseEnchants1, baseEnchants2); + + // Remove all enchantments (including EnchantApplicator bonuses) + for (Enchantment enchant : new HashSet<>(resultMeta.getEnchants().keySet())) { + resultMeta.removeEnchant(enchant); + } + + // Apply merged base enchantments + for (Map.Entry entry : mergedEnchants.entrySet()) { + resultMeta.addEnchant(entry.getKey(), entry.getValue(), true); + } + + // Re-apply EnchantApplicator bonuses from first item's runes + for (Rune rune : runes1) { + if (rune.getType().getActivator() instanceof ItemApplicationHandler itemApplicationHandler) { + try { + itemApplicationHandler.applyOnItems(plugin, resultMeta, rune.getConfiguration()); + } catch (Exception e) { + plugin.getLogger().warning("Failed to reapply EnchantApplicator from rune " + rune.getName() + ": " + e.getMessage()); + } + } + } + + // Update PDC with all runes (first item + new runes from second item) + List allRunes = new ArrayList<>(runes1); + for (Rune rune2 : runes2) { + if (!runes1.contains(rune2)) { + allRunes.add(rune2); + } + } + + PersistentDataContainer pdc = resultMeta.getPersistentDataContainer(); + pdc.set(runeManager.getKey(), PersistentDataType.LIST.listTypeFrom(runeManager.getDataType()), allRunes); + + // Add lore lines for NEW runes from second item using ItemComponent (preserves colors) + for (Rune rune2 : runes2) { + if (!runes1.contains(rune2)) { + String runeLine = getMessage(Message.RUNE_LINE, "%rune%", rune2.getDisplayName()); + plugin.getItemComponent().addLoreLine(resultMeta, runeLine); + + // Apply ItemApplicationHandler if this rune has one + if (rune2.getType().getActivator() instanceof ItemApplicationHandler itemApplicationHandler) { + try { + itemApplicationHandler.applyOnItems(plugin, resultMeta, rune2.getConfiguration()); + } catch (Exception e) { + plugin.getLogger().warning("Failed to apply ItemApplicationHandler from new rune " + rune2.getName() + ": " + e.getMessage()); + } + } + } + } + + result.setItemMeta(resultMeta); + event.setResult(result); + } + + /** + * Checks if two runes are incompatible with each other. + */ + private boolean areRunesIncompatible(Rune rune1, Rune rune2) { + return rune1.getType().getIncompatibles().contains(rune2.getType()) || + rune2.getType().getIncompatibles().contains(rune1.getType()); + } + + /** + * Calculates the base enchantments of an item without EnchantApplicator bonuses. + * This extracts the "real" enchantments by removing the bonus levels added by runes. + * Also handles enchanted books which store enchantments in EnchantmentStorageMeta. + * + * Example: Item has Effi 3 displayed, with EnchantApplicator +1 rune + * → Base enchantment is Effi 2 + */ + private Map calculateBaseEnchantments(ItemStack item, List runes) { + if (!item.hasItemMeta()) { + return new HashMap<>(); + } + + ItemMeta meta = item.getItemMeta(); + Map enchants; + + // Check if this is an enchanted book (uses EnchantmentStorageMeta) + if (meta instanceof EnchantmentStorageMeta enchantmentStorageMeta) { + enchants = new HashMap<>(enchantmentStorageMeta.getStoredEnchants()); + } else { + enchants = new HashMap<>(meta.getEnchants()); + } + + // Remove EnchantApplicator bonuses to get base enchantments + for (Rune rune : runes) { + if (rune.getType().getActivator() instanceof ItemApplicationHandler && + rune.getConfiguration() instanceof RuneEnchantApplicatorConfiguration config) { + + for (RuneEnchantApplicatorConfiguration.EnchantmentEvolution evolution : config.getEnchantmentEvolutions()) { + Enchantment enchant = evolution.enchantment(); + int evolutionValue = evolution.evolution(); + + if (enchants.containsKey(enchant)) { + int currentLevel = enchants.get(enchant); + int baseLevel = currentLevel - evolutionValue; + + if (baseLevel <= 0) { + enchants.remove(enchant); + } else { + enchants.put(enchant, baseLevel); + } + } + } + } + } + + return enchants; + } + + /** + * Merges two enchantment maps using vanilla anvil logic. + * - Same enchant, same level → level + 1 (if not exceeding max) + * - Same enchant, different level → higher level + * - Different enchants → both included (if compatible) + */ + private Map mergeEnchantments(Map enchants1, Map enchants2) { + Map merged = new HashMap<>(enchants1); + + for (Map.Entry entry : enchants2.entrySet()) { + Enchantment enchant = entry.getKey(); + int level2 = entry.getValue(); + + if (merged.containsKey(enchant)) { + int level1 = merged.get(enchant); + + if (level1 == level2) { + // Same level: upgrade by 1 if possible (vanilla anvil behavior) + int newLevel = Math.min(level1 + 1, enchant.getMaxLevel()); + merged.put(enchant, newLevel); + } else { + // Different levels: take the higher one + merged.put(enchant, Math.max(level1, level2)); + } + } else { + // Enchantment only on second item: add it if compatible + boolean compatible = true; + for (Enchantment existingEnchant : merged.keySet()) { + if (enchant.conflictsWith(existingEnchant)) { + compatible = false; + break; + } + } + + if (compatible) { + merged.put(enchant, level2); + } + } + } + + return merged; + } +} \ No newline at end of file diff --git a/src/main/java/fr/maxlego08/items/listener/CommandsListener.java b/src/main/java/fr/maxlego08/items/listener/CommandsListener.java index 43ada76..bc32e53 100644 --- a/src/main/java/fr/maxlego08/items/listener/CommandsListener.java +++ b/src/main/java/fr/maxlego08/items/listener/CommandsListener.java @@ -9,7 +9,6 @@ import fr.maxlego08.items.api.configurations.commands.ItemCommand; import fr.maxlego08.items.zcore.utils.ZUtils; import fr.maxlego08.items.zcore.utils.builder.CooldownBuilder; -import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -100,8 +99,6 @@ private void useItem(Player player, CommandsConfiguration commandConfiguration, for (ItemCommand itemCommand : commands.stream().filter(command -> command.action() == itemAction || command.action() == fr.maxlego08.items.api.configurations.commands.Action.CLICK).collect(Collectors.toSet())) { if (itemCommand.cooldown() > 0 && CooldownBuilder.isCooldown(this.generateCooldownName(item, itemCommand), player.getUniqueId())) { - //TODO add message - Bukkit.getLogger().info("ToDo Add Message - ItemCooldown"); return; } diff --git a/src/main/java/fr/maxlego08/items/listener/ListenerAdapter.java b/src/main/java/fr/maxlego08/items/listener/ListenerAdapter.java deleted file mode 100644 index f4f86f9..0000000 --- a/src/main/java/fr/maxlego08/items/listener/ListenerAdapter.java +++ /dev/null @@ -1,93 +0,0 @@ -package fr.maxlego08.items.listener; - -import fr.maxlego08.items.zcore.utils.ZUtils; -import org.bukkit.Location; -import org.bukkit.block.Block; -import org.bukkit.entity.*; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.block.BlockPlaceEvent; -import org.bukkit.event.entity.CreatureSpawnEvent; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDamageEvent.DamageCause; -import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.event.inventory.CraftItemEvent; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.inventory.InventoryCloseEvent; -import org.bukkit.event.inventory.InventoryDragEvent; -import org.bukkit.event.player.*; - -@SuppressWarnings("deprecation") -public abstract class ListenerAdapter extends ZUtils{ - - protected void onConnect(PlayerJoinEvent event, Player player) { - } - - protected void onQuit(PlayerQuitEvent event, Player player) { - } - - protected void onMove(PlayerMoveEvent event, Player player) { - } - - protected void onInventoryClick(InventoryClickEvent event, Player player) { - } - - protected void onInventoryClose(InventoryCloseEvent event, Player player) { - } - - protected void onInventoryDrag(InventoryDragEvent event, Player player) { - } - - protected void onBlockBreak(BlockBreakEvent event, Player player) { - } - - protected void onBlockPlace(BlockPlaceEvent event, Player player) { - } - - protected void onEntityDeath(EntityDeathEvent event, Entity entity) { - } - - protected void onInteract(PlayerInteractEvent event, Player player) { - } - - protected void onPlayerTalk(AsyncPlayerChatEvent event, String message) { - } - - protected void onCraftItem(CraftItemEvent event) { - } - - protected void onCommand(PlayerCommandPreprocessEvent event, Player player, String message) { - } - - protected void onGamemodeChange(PlayerGameModeChangeEvent event, Player player) { - } - - protected void onDrop(PlayerDropItemEvent event, Player player) { - } - - protected void onPickUp(PlayerPickupItemEvent event, Player player) { - } - - protected void onMobSpawn(CreatureSpawnEvent event) { - } - - protected void onDamageByEntity(EntityDamageByEntityEvent event, DamageCause cause, double damage, LivingEntity damager, - LivingEntity entity) { - } - - protected void onPlayerDamagaByPlayer(EntityDamageByEntityEvent event, DamageCause cause, double damage, - Player damager, Player entity) { - } - - protected void onPlayerDamagaByArrow(EntityDamageByEntityEvent event, DamageCause cause, double damage, - Projectile damager, Player entity) { - } - - protected void onItemisOnGround(PlayerDropItemEvent event, Player player, Item item, Location location) { - } - - protected void onItemMove(PlayerDropItemEvent event, Player player, Item item, Location location, Block block) { - } - - protected void onPlayerWalk(PlayerMoveEvent event, Player player, int i) { - } -} diff --git a/src/main/java/fr/maxlego08/items/listener/SpawnerListener.java b/src/main/java/fr/maxlego08/items/listener/SpawnerListener.java index 342ed1f..5afb12c 100644 --- a/src/main/java/fr/maxlego08/items/listener/SpawnerListener.java +++ b/src/main/java/fr/maxlego08/items/listener/SpawnerListener.java @@ -5,16 +5,17 @@ import org.bukkit.block.CreatureSpawner; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.BlockStateMeta; import org.bukkit.inventory.meta.ItemMeta; -public class SpawnerListener extends ListenerAdapter { - - @Override - protected void onBlockPlace(BlockPlaceEvent event, Player player) { +public class SpawnerListener implements Listener { + @EventHandler + protected void onBlockPlace(BlockPlaceEvent event) { Block block = event.getBlock(); Material material = block.getType(); if (material != Material.SPAWNER) return; diff --git a/src/main/java/fr/maxlego08/items/runes/RuneListener.java b/src/main/java/fr/maxlego08/items/runes/RuneListener.java index 7c8ff83..d46f606 100644 --- a/src/main/java/fr/maxlego08/items/runes/RuneListener.java +++ b/src/main/java/fr/maxlego08/items/runes/RuneListener.java @@ -4,7 +4,7 @@ import fr.maxlego08.items.ItemsPlugin; import fr.maxlego08.items.api.events.CustomBlockBreakEvent; import fr.maxlego08.items.api.runes.RuneManager; -import fr.maxlego08.items.api.runes.RunePipeline; +import fr.maxlego08.items.runes.RunePipeline; import fr.maxlego08.items.api.runes.handlers.InventorySlotChangeHandler; import io.papermc.paper.event.player.PlayerInventorySlotChangeEvent; import org.bukkit.entity.Player; @@ -13,8 +13,15 @@ import org.bukkit.event.Listener; import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.event.player.*; +import org.bukkit.event.player.PlayerBucketEmptyEvent; +import org.bukkit.event.player.PlayerBucketFillEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerItemHeldEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MainHand; import java.util.ArrayList; @@ -118,4 +125,34 @@ public void onEntityDeath(EntityDeathEvent event) { public void onInteract(PlayerInteractEvent event) { this.runeManager.onPlayerEvent(event); } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onBucketEmpty(PlayerBucketEmptyEvent event) { + if (event.isCancelled()) return; + + var player = event.getPlayer(); + var itemStack = event.getHand() == EquipmentSlot.HAND + ? event.getPlayer().getInventory().getItemInMainHand() + : event.getPlayer().getInventory().getItemInOffHand(); + var optional = this.runeManager.getRunes(itemStack); + if (optional.isEmpty()) return; + + var runes = new ArrayList<>(optional.get()); + RunePipeline pipeline = new RunePipeline(runes); + pipeline.pipeline(plugin, event); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onBucketFill(PlayerBucketFillEvent event) { + if (event.isCancelled()) return; + var itemStack = event.getHand() == EquipmentSlot.HAND + ? event.getPlayer().getInventory().getItemInMainHand() + : event.getPlayer().getInventory().getItemInOffHand(); + var optional = this.runeManager.getRunes(itemStack); + if (optional.isEmpty()) return; + + var runes = new ArrayList<>(optional.get()); + RunePipeline pipeline = new RunePipeline(runes); + pipeline.pipeline(plugin, event); + } } diff --git a/API/src/main/java/fr/maxlego08/items/api/runes/RunePipeline.java b/src/main/java/fr/maxlego08/items/runes/RunePipeline.java similarity index 55% rename from API/src/main/java/fr/maxlego08/items/api/runes/RunePipeline.java rename to src/main/java/fr/maxlego08/items/runes/RunePipeline.java index f7ae672..919f47a 100644 --- a/API/src/main/java/fr/maxlego08/items/api/runes/RunePipeline.java +++ b/src/main/java/fr/maxlego08/items/runes/RunePipeline.java @@ -1,14 +1,10 @@ -package fr.maxlego08.items.api.runes; +package fr.maxlego08.items.runes; import fr.maxlego08.items.api.ItemPlugin; import fr.maxlego08.items.api.hook.jobs.JobsExpGainEventWrapper; import fr.maxlego08.items.api.hook.jobs.JobsPayementEventWrapper; -import fr.maxlego08.items.api.runes.handlers.BreakHandler; -import fr.maxlego08.items.api.runes.handlers.EntityDeathHandler; -import fr.maxlego08.items.api.runes.handlers.InteractionHandler; -import fr.maxlego08.items.api.runes.handlers.InventorySlotChangeHandler; -import fr.maxlego08.items.api.runes.handlers.JobsExperienceHandler; -import fr.maxlego08.items.api.runes.handlers.JobsMoneyHandler; +import fr.maxlego08.items.api.runes.Rune; +import fr.maxlego08.items.api.runes.handlers.*; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; @@ -16,24 +12,42 @@ import org.bukkit.event.Event; import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.player.PlayerBucketEmptyEvent; +import org.bukkit.event.player.PlayerBucketFillEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.ItemStack; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; public class RunePipeline { private final List runes; + // Map containing runes filtered by handler type for performance + private final Map, List> runesByHandler; + + // List of all handler types to initialize + private static final List> HANDLER_TYPES = List.of( + BreakHandler.class, + InventorySlotChangeHandler.class, + InteractionHandler.class, + JobsExperienceHandler.class, + JobsMoneyHandler.class, + EntityDeathHandler.class, + BucketHandler.class + ); public RunePipeline(List activators) { activators.sort(Comparator.comparingInt(rune -> rune.getType().getActivator().getPriority())); this.runes = activators.reversed(); + + // Pre-filter runes by handler type once during initialization + this.runesByHandler = new HashMap<>(); + for (Class handlerType : HANDLER_TYPES) { + List filteredRunes = runes.stream() + .filter(rune -> handlerType.isInstance(rune.getType().getActivator())) + .toList(); + runesByHandler.put(handlerType, filteredRunes); + } } private void handleBreak(ItemPlugin plugin, BlockBreakEvent event) { @@ -50,22 +64,22 @@ private void handleBreak(ItemPlugin plugin, BlockBreakEvent event) { } private Set breakBlocks(ItemPlugin plugin, BlockBreakEvent event, Map> drops) { - - var activeRunes = runes.stream().filter(rune -> rune.getType().getActivator() instanceof BreakHandler).toList(); - if (activeRunes.isEmpty()) return new HashSet<>(); + List breakHandlerRunes = runesByHandler.get(BreakHandler.class); + if (breakHandlerRunes.isEmpty()) return new HashSet<>(); Set currentBlocks = new HashSet<>(); currentBlocks.add(event.getBlock()); drops.put(event.getBlock().getLocation(), new ArrayList<>(event.getBlock().getDrops(event.getPlayer().getInventory().getItemInMainHand()))); - for (Rune rune : activeRunes) { + for (Rune rune : breakHandlerRunes) { currentBlocks = new HashSet<>(((BreakHandler) rune.getType().getActivator()).breakBlocks(plugin, event, rune.getConfiguration(), new HashSet<>(currentBlocks), drops)); } return currentBlocks; } public void pipeline(ItemPlugin plugin, Player player, InventorySlotChangeHandler.InventorySlotChangeType type) { - for (Rune rune : runes.stream().filter(rune -> rune.getType().getActivator() instanceof InventorySlotChangeHandler).toList()) { + List inventorySlotChangeRunes = runesByHandler.get(InventorySlotChangeHandler.class); + for (Rune rune : inventorySlotChangeRunes) { if (type == ((InventorySlotChangeHandler) rune.getType().getActivator()).getType(rune.getConfiguration())) { ((InventorySlotChangeHandler) rune.getType().getActivator()).onInventorySlotChange(plugin, player, rune.getConfiguration()); } @@ -75,26 +89,42 @@ public void pipeline(ItemPlugin plugin, Player player, InventorySlotChangeHandle public void pipeline(ItemPlugin plugin, T event) { switch (event) { case PlayerInteractEvent playerInteractEvent -> { - for (Rune rune : runes.stream().filter(rune -> rune.getType().getActivator() instanceof InteractionHandler).toList()) { + List interactionHandlerRunes = runesByHandler.get(InteractionHandler.class); + for (Rune rune : interactionHandlerRunes) { ((InteractionHandler) rune.getType().getActivator()).interactBlock(plugin, playerInteractEvent, rune.getConfiguration()); } } case JobsExpGainEventWrapper jobsExpGainEventWrapper -> { - for (Rune rune : runes.stream().filter(rune -> rune.getType().getActivator() instanceof JobsExperienceHandler).toList()) { + List jobsExperienceRunes = runesByHandler.get(JobsExperienceHandler.class); + for (Rune rune : jobsExperienceRunes) { ((JobsExperienceHandler) rune.getType().getActivator()).jobsGainExperience(plugin, jobsExpGainEventWrapper, rune.getConfiguration()); } } case JobsPayementEventWrapper jobsPayementEventWrapper -> { - for (Rune rune : runes.stream().filter(rune -> rune.getType().getActivator() instanceof JobsMoneyHandler).toList()) { + List jobsMoneyRunes = runesByHandler.get(JobsMoneyHandler.class); + for (Rune rune : jobsMoneyRunes) { ((JobsMoneyHandler) rune.getType().getActivator()).jobsGainMoney(plugin, jobsPayementEventWrapper, rune.getConfiguration()); } } case BlockBreakEvent blockBreakEvent -> handleBreak(plugin, blockBreakEvent); case EntityDeathEvent entityDeathEvent -> { - for (Rune rune : runes.stream().filter(rune -> rune.getType().getActivator() instanceof EntityDeathHandler).toList()) { + List entityDeathRunes = runesByHandler.get(EntityDeathHandler.class); + for (Rune rune : entityDeathRunes) { ((EntityDeathHandler) rune.getType().getActivator()).onEntityDeath(plugin, entityDeathEvent, rune.getConfiguration()); } } + case PlayerBucketEmptyEvent bucketEmptyEvent -> { + List bucketHandlerRunes = runesByHandler.get(BucketHandler.class); + for (Rune rune : bucketHandlerRunes) { + ((BucketHandler) rune.getType().getActivator()).onBucketEmpty(plugin, bucketEmptyEvent, rune.getConfiguration()); + } + } + case PlayerBucketFillEvent bucketFillEvent -> { + List bucketHandlerRunes = runesByHandler.get(BucketHandler.class); + for (Rune rune : bucketHandlerRunes) { + ((BucketHandler) rune.getType().getActivator()).onBucketFill(plugin, bucketFillEvent, rune.getConfiguration()); + } + } default -> throw new IllegalStateException("Unexpected value: " + event); } } diff --git a/src/main/java/fr/maxlego08/items/runes/ZRuneManager.java b/src/main/java/fr/maxlego08/items/runes/ZRuneManager.java index fdbf1ca..97fc554 100644 --- a/src/main/java/fr/maxlego08/items/runes/ZRuneManager.java +++ b/src/main/java/fr/maxlego08/items/runes/ZRuneManager.java @@ -6,15 +6,11 @@ import fr.maxlego08.items.api.recipes.ZItemIngredient; import fr.maxlego08.items.api.runes.Rune; import fr.maxlego08.items.api.runes.RuneManager; -import fr.maxlego08.items.api.runes.RunePipeline; +import fr.maxlego08.items.runes.RunePipeline; import fr.maxlego08.items.api.runes.RuneType; import fr.maxlego08.items.api.runes.applicators.Applicator; import fr.maxlego08.items.api.runes.configurations.RuneConfiguration; -import fr.maxlego08.items.api.runes.exceptions.ItemContainsAlreadyRuneException; -import fr.maxlego08.items.api.runes.exceptions.NoMetaException; -import fr.maxlego08.items.api.runes.exceptions.RuneAppliedException; -import fr.maxlego08.items.api.runes.exceptions.RuneException; -import fr.maxlego08.items.api.runes.exceptions.RuneNotAllowedException; +import fr.maxlego08.items.api.runes.exceptions.*; import fr.maxlego08.items.api.runes.handlers.ItemApplicationHandler; import fr.maxlego08.items.api.utils.TagRegistry; import fr.maxlego08.items.zcore.enums.Message; @@ -41,16 +37,10 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -100,7 +90,7 @@ public void loadRunes() { try (Stream stream = Files.walk(folder.toPath())) { stream.skip(1).map(Path::toFile).filter(File::isFile).filter(e -> e.getName().endsWith(".yml")).forEach(this::loadRune); } catch (IOException exception) { - exception.printStackTrace(); + plugin.getLogger().log(Level.SEVERE, "Failed to load runes from folder: " + folder.getPath(), exception); } } @@ -135,8 +125,7 @@ public void loadRune(File file) { plugin.info("Loaded rune " + file.getPath()); } catch (Exception exception) { - logger.severe("Unable to load the rune " + file.getPath()); - exception.printStackTrace(); + logger.log(Level.SEVERE, "Failed to load rune from file: " + file.getPath(), exception); } } @@ -239,7 +228,7 @@ public void applyRune(ItemStack itemStack, Rune rune) throws RuneException { AtomicInteger line = new AtomicInteger(); AtomicBoolean removeParent = new AtomicBoolean(false); this.getRune(rune.getParent()).ifPresent(parent -> { - String displayRune = color(getMessage(Message.RUNE_LINE, "%rune%", parent.getDisplayName())); + String displayRune = getMessage(Message.RUNE_LINE, "%rune%", parent.getDisplayName()); line.set(lore.indexOf(displayRune)); if (line.get() != -1) { lore.remove(line.get()); @@ -247,12 +236,12 @@ public void applyRune(ItemStack itemStack, Rune rune) throws RuneException { } }); if (removeParent.get()) { - lore.set(line.get(), color(getMessage(Message.RUNE_LINE, "%rune%", rune.getDisplayName()))); + lore.set(line.get(), getMessage(Message.RUNE_LINE, "%rune%", rune.getDisplayName())); } else { if (nbRunesView != -1 && runes.size() == nbRunesView) { - lore.add(color(getMessage(Message.RUNE_MORE))); + lore.add(getMessage(Message.RUNE_MORE)); } else { - lore.add(color(getMessage(Message.RUNE_LINE, "%rune%", rune.getDisplayName()))); + lore.add(getMessage(Message.RUNE_LINE, "%rune%", rune.getDisplayName())); } } @@ -260,7 +249,7 @@ public void applyRune(ItemStack itemStack, Rune rune) throws RuneException { } } - itemMeta.setLore(lore); + this.plugin.getItemComponent().setLore(itemMeta, lore); runes.add(rune); persistentDataContainer.set(this.namespacedKey, PersistentDataType.LIST.listTypeFrom(this.runeDataType), runes); @@ -326,10 +315,9 @@ public Map> getRecipesUseRunes() { private List generateRuneLore(Rune rune) { List runeLore = Message.RUNE_LORE.getMessages(); - List formattedLore = new ArrayList<>(); - runeLore.forEach(line -> formattedLore.add(color(line))); - formattedLore.add(color(getMessage(Message.RUNE_LINE, "%rune%", rune.getDisplayName()))); + List formattedLore = new ArrayList<>(runeLore); + formattedLore.add(getMessage(Message.RUNE_LINE, "%rune%", rune.getDisplayName())); return formattedLore; } @@ -376,11 +364,6 @@ private void createCustomApplicator(Item runeItem) { int nbExtra = ingredients.size() - nbInputs - 1; for (Material material : materials) { ItemStack result = new ItemStack(material); - /*try { - this.plugin.getRuneManager().applyRune(result, rune); - } catch (RuneException exception) { - exception.printStackTrace(); - }*/ List ingredientsInner = new ArrayList<>(ingredients); ingredientsInner.add(new MaterialIngredient(material)); ItemRecipe recipe = new ItemRecipe("rune_" + rune.getName() + "_" + material.name().toLowerCase() + "_applicator", "", "", null, result, 1, ingredientsInner.toArray(Ingredient[]::new), null, 0, 0); diff --git a/src/main/java/fr/maxlego08/items/runes/activators/Empty.java b/src/main/java/fr/maxlego08/items/runes/activators/Empty.java new file mode 100644 index 0000000..7f1d1ea --- /dev/null +++ b/src/main/java/fr/maxlego08/items/runes/activators/Empty.java @@ -0,0 +1,10 @@ +package fr.maxlego08.items.runes.activators; + +import fr.maxlego08.items.api.runes.RuneActivator; + +public class Empty implements RuneActivator { + @Override + public int getPriority() { + return -1; + } +} diff --git a/src/main/java/fr/maxlego08/items/runes/activators/InfiniteBucket.java b/src/main/java/fr/maxlego08/items/runes/activators/InfiniteBucket.java new file mode 100644 index 0000000..920b394 --- /dev/null +++ b/src/main/java/fr/maxlego08/items/runes/activators/InfiniteBucket.java @@ -0,0 +1,62 @@ +package fr.maxlego08.items.runes.activators; + +import fr.maxlego08.items.api.ItemPlugin; +import fr.maxlego08.items.api.runes.RuneActivator; +import fr.maxlego08.items.api.runes.configurations.EmptyConfiguration; +import fr.maxlego08.items.api.runes.configurations.RuneConfiguration; +import fr.maxlego08.items.api.runes.handlers.BucketHandler; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.event.player.PlayerBucketEmptyEvent; +import org.bukkit.event.player.PlayerBucketFillEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * Infinite Bucket Rune Activator + *

+ * Makes buckets infinite based on their type: + * - Empty bucket: Stays empty (infinite empty bucket for picking up fluids) + * - Water bucket: Never empties (infinite water source) + * - Lava bucket: Never empties (infinite lava source) + * - Other filled buckets: Never empty + */ +public class InfiniteBucket implements BucketHandler, RuneActivator { + + private static final List FORBIDDEN_MATERIALS = List.of( + Material.MILK_BUCKET, + Material.POWDER_SNOW_BUCKET + ); + + @Override + public void onBucketEmpty(ItemPlugin plugin, PlayerBucketEmptyEvent event, EmptyConfiguration runeConfiguration) { + Material bucketType = event.getBucket(); + if(FORBIDDEN_MATERIALS.contains(bucketType)) { + return; + } + event.setCancelled(true); + Block block = event.getBlockClicked().getRelative(event.getBlockFace()); + block.setType(this.getFilledBlockMaterial(bucketType)); + } + + private @NotNull Material getFilledBlockMaterial(Material bucketType) { + return switch (bucketType) { + case LAVA_BUCKET -> Material.LAVA; + default -> Material.WATER; + }; + } + + @Override + public void onBucketFill(ItemPlugin plugin, PlayerBucketFillEvent event, EmptyConfiguration runeConfiguration) { + event.setCancelled(true); + event.getBlockClicked().setType(Material.AIR); + } + + @Override + public int getPriority() { + return 0; + } +} \ No newline at end of file diff --git a/src/main/java/fr/maxlego08/items/runes/activators/SlotChange.java b/src/main/java/fr/maxlego08/items/runes/activators/SlotChange.java index 84d8ee1..6ab4c24 100644 --- a/src/main/java/fr/maxlego08/items/runes/activators/SlotChange.java +++ b/src/main/java/fr/maxlego08/items/runes/activators/SlotChange.java @@ -5,7 +5,6 @@ import fr.maxlego08.items.api.runes.configurations.SlotChangeConfiguration; import fr.maxlego08.items.api.runes.handlers.InventorySlotChangeHandler; import fr.maxlego08.items.zcore.utils.ZUtils; -import io.papermc.paper.event.player.PlayerInventorySlotChangeEvent; import org.bukkit.entity.Player; public class SlotChange extends ZUtils implements RuneActivator, InventorySlotChangeHandler { diff --git a/src/main/java/fr/maxlego08/items/runes/activators/VeinMiner.java b/src/main/java/fr/maxlego08/items/runes/activators/VeinMiner.java index fe3f094..2c5ab9a 100644 --- a/src/main/java/fr/maxlego08/items/runes/activators/VeinMiner.java +++ b/src/main/java/fr/maxlego08/items/runes/activators/VeinMiner.java @@ -10,13 +10,7 @@ import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.inventory.ItemStack; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Queue; -import java.util.Set; +import java.util.*; public class VeinMiner implements BreakHandler, RuneActivator { diff --git a/src/main/java/fr/maxlego08/items/save/MessageLoader.java b/src/main/java/fr/maxlego08/items/save/MessageLoader.java index 570f8e7..f1502ac 100644 --- a/src/main/java/fr/maxlego08/items/save/MessageLoader.java +++ b/src/main/java/fr/maxlego08/items/save/MessageLoader.java @@ -3,9 +3,9 @@ import fr.maxlego08.items.zcore.enums.Message; import fr.maxlego08.items.zcore.enums.MessageType; import fr.maxlego08.items.zcore.logger.Logger; +import fr.maxlego08.items.zcore.utils.ZUtils; import fr.maxlego08.items.zcore.utils.storage.Persist; import fr.maxlego08.items.zcore.utils.storage.Savable; -import fr.maxlego08.items.zcore.utils.yaml.YamlUtils; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.plugin.java.JavaPlugin; @@ -15,14 +15,16 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.logging.Level; /** * The MessageLoader class extends YamlUtils and implements Savable to manage message configurations. * This class is responsible for loading and saving custom messages to a YAML files for a Bukkit plugin. */ -public class MessageLoader extends YamlUtils implements Savable { +public class MessageLoader extends ZUtils implements Savable { + private final JavaPlugin plugin; private final List loadedMessages = new ArrayList<>(); /** @@ -31,7 +33,7 @@ public class MessageLoader extends YamlUtils implements Savable { * @param plugin The JavaPlugin instance associated with this loader. */ public MessageLoader(JavaPlugin plugin) { - super(plugin); + this.plugin = plugin; } /** @@ -49,11 +51,11 @@ public void save(Persist persist) { try { file.createNewFile(); } catch (IOException exception) { - exception.printStackTrace(); + plugin.getLogger().log(Level.SEVERE, "Failed to create messages.yml file", exception); } } - YamlConfiguration configuration = getConfig(file); + YamlConfiguration configuration = YamlConfiguration.loadConfiguration(file); for (Message message : Message.values()) { if (!message.isUse()) continue; @@ -93,7 +95,7 @@ public void save(Persist persist) { try { configuration.save(file); } catch (IOException exception) { - exception.printStackTrace(); + plugin.getLogger().log(Level.SEVERE, "Failed to save messages.yml file", exception); } loadMessages(configuration); @@ -113,7 +115,7 @@ public void load(Persist persist) { return; } - YamlConfiguration configuration = getConfig(file); + YamlConfiguration configuration = YamlConfiguration.loadConfiguration(file); this.save(null); loadMessages(configuration); @@ -180,8 +182,8 @@ private void loadMessage(YamlConfiguration configuration, String key) { int showTime = configuration.getInt(key + ".showTime"); int fadeOutTime = configuration.getInt(key + ".fadeOutTime"); Map titles = new HashMap<>(); - titles.put("title", color(title)); - titles.put("subtitle", color(subtitle)); + titles.put("title",title); + titles.put("subtitle", subtitle); titles.put("start", fadeInTime); titles.put("time", showTime); titles.put("end", fadeOutTime); diff --git a/src/main/java/fr/maxlego08/items/scoreboard/FastBoard.java b/src/main/java/fr/maxlego08/items/scoreboard/FastBoard.java deleted file mode 100644 index c742a68..0000000 --- a/src/main/java/fr/maxlego08/items/scoreboard/FastBoard.java +++ /dev/null @@ -1,621 +0,0 @@ -/* - * This files is part of FastBoard, licensed under the MIT License. - * - * Copyright (c) 2019-2021 MrMicky - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package fr.maxlego08.items.scoreboard; - -import org.bukkit.ChatColor; -import org.bukkit.entity.Player; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.lang.reflect.Array; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.*; -import java.util.concurrent.ThreadLocalRandom; - -/** - * Lightweight packet-based scoreboard API for Bukkit plugins. - * It can be safely used asynchronously as everything is at packet level. - *

- * The project is on GitHub. - * - * @author MrMicky - * @version 1.2.0 - */ -public class FastBoard { - - private static final Map, Field[]> PACKETS = new HashMap<>(8); - private static final String[] COLOR_CODES = Arrays.stream(ChatColor.values()) - .map(Object::toString) - .toArray(String[]::new); - private static final VersionType VERSION_TYPE; - // Packets and components - private static final Class CHAT_COMPONENT_CLASS; - private static final Class CHAT_FORMAT_ENUM; - private static final Object EMPTY_MESSAGE; - private static final Object RESET_FORMATTING; - private static final MethodHandle MESSAGE_FROM_STRING; - private static final MethodHandle PLAYER_CONNECTION; - private static final MethodHandle SEND_PACKET; - private static final MethodHandle PLAYER_GET_HANDLE; - // Scoreboard packets - private static final FastReflection.PacketConstructor PACKET_SB_OBJ; - private static final FastReflection.PacketConstructor PACKET_SB_DISPLAY_OBJ; - private static final FastReflection.PacketConstructor PACKET_SB_SCORE; - private static final FastReflection.PacketConstructor PACKET_SB_TEAM; - private static final FastReflection.PacketConstructor PACKET_SB_SERIALIZABLE_TEAM; - // Scoreboard enums - private static final Class ENUM_SB_HEALTH_DISPLAY; - private static final Class ENUM_SB_ACTION; - private static final Object ENUM_SB_HEALTH_DISPLAY_INTEGER; - private static final Object ENUM_SB_ACTION_CHANGE; - private static final Object ENUM_SB_ACTION_REMOVE; - - static { - try { - MethodHandles.Lookup lookup = MethodHandles.lookup(); - - if (FastReflection.isRepackaged()) { - VERSION_TYPE = VersionType.V1_17; - } else if (FastReflection.nmsOptionalClass(null, "ScoreboardServer$Action").isPresent()) { - VERSION_TYPE = VersionType.V1_13; - } else if (FastReflection.nmsOptionalClass(null, "IScoreboardCriteria$EnumScoreboardHealthDisplay").isPresent()) { - VERSION_TYPE = VersionType.V1_8; - } else { - VERSION_TYPE = VersionType.V1_7; - } - - String gameProtocolPackage = "network.protocol.game"; - Class craftPlayerClass = FastReflection.obcClass("entity.CraftPlayer"); - Class craftChatMessageClass = FastReflection.obcClass("util.CraftChatMessage"); - Class entityPlayerClass = FastReflection.nmsClass("server.level", "EntityPlayer"); - Class playerConnectionClass = FastReflection.nmsClass("server.network", "PlayerConnection"); - Class packetClass = FastReflection.nmsClass("network.protocol", "Packet"); - Class packetSbObjClass = FastReflection.nmsClass(gameProtocolPackage, "PacketPlayOutScoreboardObjective"); - Class packetSbDisplayObjClass = FastReflection.nmsClass(gameProtocolPackage, "PacketPlayOutScoreboardDisplayObjective"); - Class packetSbScoreClass = FastReflection.nmsClass(gameProtocolPackage, "PacketPlayOutScoreboardScore"); - Class packetSbTeamClass = FastReflection.nmsClass(gameProtocolPackage, "PacketPlayOutScoreboardTeam"); - Class sbTeamClass = VersionType.V1_17.isHigherOrEqual() - ? FastReflection.innerClass(packetSbTeamClass, innerClass -> !innerClass.isEnum()) : null; - Field playerConnectionField = Arrays.stream(entityPlayerClass.getFields()) - .filter(field -> field.getType().isAssignableFrom(playerConnectionClass)) - .findFirst().orElseThrow(NoSuchFieldException::new); - - MESSAGE_FROM_STRING = lookup.unreflect(craftChatMessageClass.getMethod("fromString", String.class)); - CHAT_COMPONENT_CLASS = FastReflection.nmsClass("network.chat", "IChatBaseComponent"); - CHAT_FORMAT_ENUM = FastReflection.nmsClass(null, "EnumChatFormat"); - EMPTY_MESSAGE = Array.get(MESSAGE_FROM_STRING.invoke(""), 0); - RESET_FORMATTING = FastReflection.enumValueOf(CHAT_FORMAT_ENUM, "RESET", 21); - PLAYER_GET_HANDLE = lookup.findVirtual(craftPlayerClass, "getHandle", MethodType.methodType(entityPlayerClass)); - PLAYER_CONNECTION = lookup.unreflectGetter(playerConnectionField); - SEND_PACKET = lookup.findVirtual(playerConnectionClass, "sendPacket", MethodType.methodType(void.class, packetClass)); - PACKET_SB_OBJ = FastReflection.findPacketConstructor(packetSbObjClass, lookup); - PACKET_SB_DISPLAY_OBJ = FastReflection.findPacketConstructor(packetSbDisplayObjClass, lookup); - PACKET_SB_SCORE = FastReflection.findPacketConstructor(packetSbScoreClass, lookup); - PACKET_SB_TEAM = FastReflection.findPacketConstructor(packetSbTeamClass, lookup); - PACKET_SB_SERIALIZABLE_TEAM = sbTeamClass == null ? null : FastReflection.findPacketConstructor(sbTeamClass, lookup); - - for (Class clazz : Arrays.asList(packetSbObjClass, packetSbDisplayObjClass, packetSbScoreClass, packetSbTeamClass, sbTeamClass)) { - if (clazz == null) { - continue; - } - Field[] fields = Arrays.stream(clazz.getDeclaredFields()) - .filter(field -> !Modifier.isStatic(field.getModifiers())) - .toArray(Field[]::new); - for (Field field : fields) { - field.setAccessible(true); - } - PACKETS.put(clazz, fields); - } - - if (VersionType.V1_8.isHigherOrEqual()) { - String enumSbActionClass = VersionType.V1_13.isHigherOrEqual() - ? "ScoreboardServer$Action" - : "PacketPlayOutScoreboardScore$EnumScoreboardAction"; - ENUM_SB_HEALTH_DISPLAY = FastReflection.nmsClass("world.scores.criteria", "IScoreboardCriteria$EnumScoreboardHealthDisplay"); - ENUM_SB_ACTION = FastReflection.nmsClass("server", enumSbActionClass); - ENUM_SB_HEALTH_DISPLAY_INTEGER = FastReflection.enumValueOf(ENUM_SB_HEALTH_DISPLAY, "INTEGER", 0); - ENUM_SB_ACTION_CHANGE = FastReflection.enumValueOf(ENUM_SB_ACTION, "CHANGE", 0); - ENUM_SB_ACTION_REMOVE = FastReflection.enumValueOf(ENUM_SB_ACTION, "REMOVE", 1); - } else { - ENUM_SB_HEALTH_DISPLAY = null; - ENUM_SB_ACTION = null; - ENUM_SB_HEALTH_DISPLAY_INTEGER = null; - ENUM_SB_ACTION_CHANGE = null; - ENUM_SB_ACTION_REMOVE = null; - } - } catch (Throwable t) { - throw new ExceptionInInitializerError(t); - } - } - - private final Player player; - private final String id; - - private final List lines = new ArrayList<>(); - private String title = ChatColor.RESET.toString(); - - private boolean deleted = false; - - /** - * Creates a new FastBoard. - * - * @param player the owner of the scoreboard - */ - public FastBoard(Player player) { - this.player = Objects.requireNonNull(player, "player"); - this.id = "fb-" + Integer.toHexString(ThreadLocalRandom.current().nextInt()); - - try { - sendObjectivePacket(ObjectiveMode.CREATE); - sendDisplayObjectivePacket(); - } catch (Throwable t) { - throw new RuntimeException("Unable to create scoreboard", t); - } - } - - /** - * Get the scoreboard title. - * - * @return the scoreboard title - */ - public String getTitle() { - return this.title; - } - - /** - * Update the scoreboard title. - * - * @param title the new scoreboard title - * @throws IllegalArgumentException if the title is longer than 32 chars on 1.12 or lower - * @throws IllegalStateException if {@link #delete()} was call before - */ - public void updateTitle(String title) { - if (this.title.equals(Objects.requireNonNull(title, "title"))) { - return; - } - - if (!VersionType.V1_13.isHigherOrEqual() && title.length() > 32) { - throw new IllegalArgumentException("Title is longer than 32 chars"); - } - - this.title = title; - - try { - sendObjectivePacket(ObjectiveMode.UPDATE); - } catch (Throwable t) { - throw new RuntimeException("Unable to update scoreboard title", t); - } - } - - /** - * Get the scoreboard lines. - * - * @return the scoreboard lines - */ - public List getLines() { - return new ArrayList<>(this.lines); - } - - /** - * Get the specified scoreboard line. - * - * @param line the line number - * @return the line - * @throws IndexOutOfBoundsException if the line is higher than {@code size} - */ - public String getLine(int line) { - checkLineNumber(line, true, false); - - return this.lines.get(line); - } - - /** - * Update a single scoreboard line. - * - * @param line the line number - * @param text the new line text - * @throws IndexOutOfBoundsException if the line is higher than {@link #size() size() + 1} - */ - public synchronized void updateLine(int line, String text) { - checkLineNumber(line, false, true); - - try { - if (line < size()) { - this.lines.set(line, text); - - sendTeamPacket(getScoreByLine(line), TeamMode.UPDATE); - return; - } - - List newLines = new ArrayList<>(this.lines); - - if (line > size()) { - for (int i = size(); i < line; i++) { - newLines.add(""); - } - } - - newLines.add(text); - - updateLines(newLines); - } catch (Throwable t) { - throw new RuntimeException("Unable to update scoreboard lines", t); - } - } - - /** - * Remove a scoreboard line. - * - * @param line the line number - */ - public synchronized void removeLine(int line) { - checkLineNumber(line, false, false); - - if (line >= size()) { - return; - } - - List newLines = new ArrayList<>(this.lines); - newLines.remove(line); - updateLines(newLines); - } - - /** - * Update all the scoreboard lines. - * - * @param lines the new lines - * @throws IllegalArgumentException if one line is longer than 30 chars on 1.12 or lower - * @throws IllegalStateException if {@link #delete()} was call before - */ - public void updateLines(String... lines) { - updateLines(Arrays.asList(lines)); - } - - /** - * Update the lines of the scoreboard - * - * @param lines the new scoreboard lines - * @throws IllegalArgumentException if one line is longer than 30 chars on 1.12 or lower - * @throws IllegalStateException if {@link #delete()} was call before - */ - public synchronized void updateLines(Collection lines) { - Objects.requireNonNull(lines, "lines"); - checkLineNumber(lines.size(), false, true); - - if (!VersionType.V1_13.isHigherOrEqual()) { - int lineCount = 0; - for (String s : lines) { - if (s != null && s.length() > 30) { - throw new IllegalArgumentException("Line " + lineCount + " is longer than 30 chars"); - } - lineCount++; - } - } - - List oldLines = new ArrayList<>(this.lines); - this.lines.clear(); - this.lines.addAll(lines); - - int linesSize = this.lines.size(); - - try { - if (oldLines.size() != linesSize) { - List oldLinesCopy = new ArrayList<>(oldLines); - - if (oldLines.size() > linesSize) { - for (int i = oldLinesCopy.size(); i > linesSize; i--) { - sendTeamPacket(i - 1, TeamMode.REMOVE); - sendScorePacket(i - 1, ScoreboardAction.REMOVE); - - oldLines.remove(0); - } - } else { - for (int i = oldLinesCopy.size(); i < linesSize; i++) { - sendScorePacket(i, ScoreboardAction.CHANGE); - sendTeamPacket(i, TeamMode.CREATE); - - oldLines.add(oldLines.size() - i, getLineByScore(i)); - } - } - } - - for (int i = 0; i < linesSize; i++) { - if (!Objects.equals(getLineByScore(oldLines, i), getLineByScore(i))) { - sendTeamPacket(i, TeamMode.UPDATE); - } - } - } catch (Throwable t) { - throw new RuntimeException("Unable to update scoreboard lines", t); - } - } - - /** - * Get the player who has the scoreboard. - * - * @return current player for this FastBoard - */ - public Player getPlayer() { - return this.player; - } - - /** - * Get the scoreboard id. - * - * @return the id - */ - public String getId() { - return this.id; - } - - /** - * Get if the scoreboard is deleted. - * - * @return true if the scoreboard is deleted - */ - public boolean isDeleted() { - return this.deleted; - } - - /** - * Get the scoreboard size (the number of lines). - * - * @return the size - */ - public int size() { - return this.lines.size(); - } - - /** - * Delete this FastBoard, and will remove the scoreboard for the associated player if he is online. - * After this, all uses of {@link #updateLines} and {@link #updateTitle} will throws an {@link IllegalStateException} - * - * @throws IllegalStateException if this was already call before - */ - public void delete() { - try { - for (int i = 0; i < this.lines.size(); i++) { - sendTeamPacket(i, TeamMode.REMOVE); - } - - sendObjectivePacket(ObjectiveMode.REMOVE); - } catch (Throwable t) { - throw new RuntimeException("Unable to delete scoreboard", t); - } - - this.deleted = true; - } - - /** - * Return if the player has a prefix/suffix characters limit. - * By default, it returns true only in 1.12 or lower. - * This method can be overridden to fix compatibility with some versions support plugin. - * - * @return max length - */ - protected boolean hasLinesMaxLength() { - return !VersionType.V1_13.isHigherOrEqual(); - } - - private void checkLineNumber(int line, boolean checkInRange, boolean checkMax) { - if (line < 0) { - throw new IllegalArgumentException("Line number must be positive"); - } - - if (checkInRange && line >= this.lines.size()) { - throw new IllegalArgumentException("Line number must be under " + this.lines.size()); - } - - if (checkMax && line >= COLOR_CODES.length - 1) { - throw new IllegalArgumentException("Line number is too high: " + line); - } - } - - private int getScoreByLine(int line) { - return this.lines.size() - line - 1; - } - - private String getLineByScore(int score) { - return getLineByScore(this.lines, score); - } - - private String getLineByScore(List lines, int score) { - return lines.get(lines.size() - score - 1); - } - - private void sendObjectivePacket(ObjectiveMode mode) throws Throwable { - Object packet = PACKET_SB_OBJ.invoke(); - - setField(packet, String.class, this.id); - setField(packet, int.class, mode.ordinal()); - - if (mode != ObjectiveMode.REMOVE) { - setComponentField(packet, this.title, 1); - - if (VersionType.V1_8.isHigherOrEqual()) { - setField(packet, ENUM_SB_HEALTH_DISPLAY, ENUM_SB_HEALTH_DISPLAY_INTEGER); - } - } else if (VERSION_TYPE == VersionType.V1_7) { - setField(packet, String.class, "", 1); - } - - sendPacket(packet); - } - - private void sendDisplayObjectivePacket() throws Throwable { - Object packet = PACKET_SB_DISPLAY_OBJ.invoke(); - - setField(packet, int.class, 1); // Position (1: sidebar) - setField(packet, String.class, this.id); // Score Name - - sendPacket(packet); - } - - private void sendScorePacket(int score, ScoreboardAction action) throws Throwable { - Object packet = PACKET_SB_SCORE.invoke(); - - setField(packet, String.class, COLOR_CODES[score], 0); // Player Name - - if (VersionType.V1_8.isHigherOrEqual()) { - setField(packet, ENUM_SB_ACTION, action == ScoreboardAction.REMOVE ? ENUM_SB_ACTION_REMOVE : ENUM_SB_ACTION_CHANGE); - } else { - setField(packet, int.class, action.ordinal(), 1); // Action - } - - if (action == ScoreboardAction.CHANGE) { - setField(packet, String.class, this.id, 1); // Objective Name - setField(packet, int.class, score); // Score - } - - sendPacket(packet); - } - - private void sendTeamPacket(int score, TeamMode mode) throws Throwable { - if (mode == TeamMode.ADD_PLAYERS || mode == TeamMode.REMOVE_PLAYERS) { - throw new UnsupportedOperationException(); - } - - int maxLength = hasLinesMaxLength() ? 16 : 1024; - Object packet = PACKET_SB_TEAM.invoke(); - - setField(packet, String.class, this.id + ':' + score); // Team name - setField(packet, int.class, mode.ordinal(), VERSION_TYPE == VersionType.V1_8 ? 1 : 0); // Update mode - - if (mode == TeamMode.CREATE || mode == TeamMode.UPDATE) { - String line = getLineByScore(score); - String prefix; - String suffix = null; - - if (line == null || line.isEmpty()) { - prefix = COLOR_CODES[score] + ChatColor.RESET; - } else if (line.length() <= maxLength) { - prefix = line; - } else { - // Prevent splitting color codes - int index = line.charAt(maxLength - 1) == ChatColor.COLOR_CHAR ? (maxLength - 1) : maxLength; - prefix = line.substring(0, index); - String suffixTmp = line.substring(index); - ChatColor chatColor = null; - - if (suffixTmp.length() >= 2 && suffixTmp.charAt(0) == ChatColor.COLOR_CHAR) { - chatColor = ChatColor.getByChar(suffixTmp.charAt(1)); - } - - String color = ChatColor.getLastColors(prefix); - boolean addColor = chatColor == null || chatColor.isFormat(); - - suffix = (addColor ? (color.isEmpty() ? ChatColor.RESET.toString() : color) : "") + suffixTmp; - } - - if (prefix.length() > maxLength || (suffix != null && suffix.length() > maxLength)) { - // Something went wrong, just cut to prevent client crash/kick - prefix = prefix.substring(0, maxLength); - suffix = (suffix != null) ? suffix.substring(0, maxLength) : null; - } - - if (VersionType.V1_17.isHigherOrEqual()) { - Object team = PACKET_SB_SERIALIZABLE_TEAM.invoke(); - // Since the packet is initialized with null values, we need to change more things. - setComponentField(team, "", 0); // Display name - setField(team, CHAT_FORMAT_ENUM, RESET_FORMATTING); // Color - setComponentField(team, prefix, 1); // Prefix - setComponentField(team, suffix == null ? "" : suffix, 2); // Suffix - setField(team, String.class, "always", 0); // Visibility - setField(team, String.class, "always", 1); // Collisions - setField(packet, Optional.class, Optional.of(team)); - } else { - setComponentField(packet, prefix, 2); // Prefix - setComponentField(packet, suffix == null ? "" : suffix, 3); // Suffix - setField(packet, String.class, "always", 4); // Visibility for 1.8+ - setField(packet, String.class, "always", 5); // Collisions for 1.9+ - } - - if (mode == TeamMode.CREATE) { - setField(packet, Collection.class, Collections.singletonList(COLOR_CODES[score])); // Players in the team - } - } - - sendPacket(packet); - } - - private void sendPacket(Object packet) throws Throwable { - if (this.deleted) { - throw new IllegalStateException("This FastBoard is deleted"); - } - - if (this.player.isOnline()) { - Object entityPlayer = PLAYER_GET_HANDLE.invoke(this.player); - Object playerConnection = PLAYER_CONNECTION.invoke(entityPlayer); - SEND_PACKET.invoke(playerConnection, packet); - } - } - - private void setField(Object object, Class fieldType, Object value) throws ReflectiveOperationException { - setField(object, fieldType, value, 0); - } - - private void setField(Object packet, Class fieldType, Object value, int count) throws ReflectiveOperationException { - int i = 0; - for (Field field : PACKETS.get(packet.getClass())) { - if (field.getType() == fieldType && count == i++) { - field.set(packet, value); - } - } - } - - private void setComponentField(Object packet, String value, int count) throws Throwable { - if (!VersionType.V1_13.isHigherOrEqual()) { - setField(packet, String.class, value, count); - return; - } - - int i = 0; - for (Field field : PACKETS.get(packet.getClass())) { - if ((field.getType() == String.class || field.getType() == CHAT_COMPONENT_CLASS) && count == i++) { - field.set(packet, value.isEmpty() ? EMPTY_MESSAGE : Array.get(MESSAGE_FROM_STRING.invoke(value), 0)); - } - } - } - - enum ObjectiveMode { - CREATE, REMOVE, UPDATE - } - - enum TeamMode { - CREATE, REMOVE, UPDATE, ADD_PLAYERS, REMOVE_PLAYERS - } - - enum ScoreboardAction { - CHANGE, REMOVE - } - - enum VersionType { - V1_7, V1_8, V1_13, V1_17; - - public boolean isHigherOrEqual() { - return VERSION_TYPE.ordinal() >= ordinal(); - } - } -} diff --git a/src/main/java/fr/maxlego08/items/scoreboard/FastReflection.java b/src/main/java/fr/maxlego08/items/scoreboard/FastReflection.java deleted file mode 100644 index 3711ce3..0000000 --- a/src/main/java/fr/maxlego08/items/scoreboard/FastReflection.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * This files is part of FastBoard, licensed under the MIT License. - * - * Copyright (c) 2019-2021 MrMicky - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package fr.maxlego08.items.scoreboard; - -import org.bukkit.Bukkit; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.lang.reflect.Field; -import java.util.Optional; -import java.util.function.Predicate; - -/** - * Small reflection utility class to use CraftBukkit and NMS. - * - * @author MrMicky - */ -public final class FastReflection { - - private static final String NM_PACKAGE = "net.minecraft"; - public static final String OBC_PACKAGE = "org.bukkit.craftbukkit"; - public static final String NMS_PACKAGE = NM_PACKAGE + ".server"; - - public static final String VERSION = Bukkit.getServer().getClass().getPackage().getName().substring(OBC_PACKAGE.length() + 1); - - private static final MethodType VOID_METHOD_TYPE = MethodType.methodType(void.class); - private static final boolean NMS_REPACKAGED = optionalClass(NM_PACKAGE + ".network.protocol.Packet").isPresent(); - - private static volatile Object theUnsafe; - - private FastReflection() { - throw new UnsupportedOperationException(); - } - - public static boolean isRepackaged() { - return NMS_REPACKAGED; - } - - public static String nmsClassName(String post1_17package, String className) { - if (NMS_REPACKAGED) { - String classPackage = post1_17package == null ? NM_PACKAGE : NM_PACKAGE + '.' + post1_17package; - return classPackage + '.' + className; - } - return NMS_PACKAGE + '.' + VERSION + '.' + className; - } - - public static Class nmsClass(String post1_17package, String className) throws ClassNotFoundException { - return Class.forName(nmsClassName(post1_17package, className)); - } - - public static Optional> nmsOptionalClass(String post1_17package, String className) { - return optionalClass(nmsClassName(post1_17package, className)); - } - - public static String obcClassName(String className) { - return OBC_PACKAGE + '.' + VERSION + '.' + className; - } - - public static Class obcClass(String className) throws ClassNotFoundException { - return Class.forName(obcClassName(className)); - } - - public static Optional> obcOptionalClass(String className) { - return optionalClass(obcClassName(className)); - } - - public static Optional> optionalClass(String className) { - try { - return Optional.of(Class.forName(className)); - } catch (ClassNotFoundException e) { - return Optional.empty(); - } - } - - public static Object enumValueOf(Class enumClass, String enumName) { - return Enum.valueOf(enumClass.asSubclass(Enum.class), enumName); - } - - public static Object enumValueOf(Class enumClass, String enumName, int fallbackOrdinal) { - try { - return enumValueOf(enumClass, enumName); - } catch (IllegalArgumentException e) { - Object[] constants = enumClass.getEnumConstants(); - if (constants.length > fallbackOrdinal) { - return constants[fallbackOrdinal]; - } - throw e; - } - } - - static Class innerClass(Class parentClass, Predicate> classPredicate) throws ClassNotFoundException { - for (Class innerClass : parentClass.getDeclaredClasses()) { - if (classPredicate.test(innerClass)) { - return innerClass; - } - } - throw new ClassNotFoundException("No class in " + parentClass.getCanonicalName() + " matches the predicate."); - } - - public static PacketConstructor findPacketConstructor(Class packetClass, MethodHandles.Lookup lookup) throws Exception { - try { - MethodHandle constructor = lookup.findConstructor(packetClass, VOID_METHOD_TYPE); - return constructor::invoke; - } catch (NoSuchMethodException | IllegalAccessException e) { - // try below with Unsafe - } - - if (theUnsafe == null) { - synchronized (FastReflection.class) { - if (theUnsafe == null) { - Class unsafeClass = Class.forName("sun.misc.Unsafe"); - Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe"); - theUnsafeField.setAccessible(true); - theUnsafe = theUnsafeField.get(null); - } - } - } - - MethodType allocateMethodType = MethodType.methodType(Object.class, Class.class); - MethodHandle allocateMethod = lookup.findVirtual(theUnsafe.getClass(), "allocateInstance", allocateMethodType); - return () -> allocateMethod.invoke(theUnsafe, packetClass); - } - - @FunctionalInterface - interface PacketConstructor { - Object invoke() throws Throwable; - } -} diff --git a/src/main/java/fr/maxlego08/items/scoreboard/ScoreBoardManager.java b/src/main/java/fr/maxlego08/items/scoreboard/ScoreBoardManager.java deleted file mode 100644 index c98dfa4..0000000 --- a/src/main/java/fr/maxlego08/items/scoreboard/ScoreBoardManager.java +++ /dev/null @@ -1,218 +0,0 @@ -package fr.maxlego08.items.scoreboard; - -import fr.maxlego08.items.zcore.utils.ZUtils; -import fr.maxlego08.items.zcore.utils.interfaces.CollectionConsumer; -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -public class ScoreBoardManager extends ZUtils { - - private final Plugin plugin; - private final Map boards = new HashMap<>(); - private final long schedulerMillisecond; - private boolean isRunning = false; - private CollectionConsumer lines; - - public ScoreBoardManager(Plugin plugin, long schedulerMillisecond) { - super(); - this.schedulerMillisecond = schedulerMillisecond; - this.plugin = plugin; - } - - /** - * Start scheduler - */ - public void schedule() { - - if (this.isRunning) { - return; - } - - this.isRunning = true; - - scheduleFix(this.plugin, this.schedulerMillisecond, (task, canRun) -> { - - // If the task cannot continue then we do not update the scoreboard - if (!canRun) return; - - if (!this.isRunning) { - task.cancel(); - return; - } - - // if the addition of the lines is null then we stop the task - if (this.lines == null) { - task.cancel(); - return; - } - - Iterator iterator = this.boards.values().iterator(); - while (iterator.hasNext()) { - FastBoard b = iterator.next(); - if (b.isDeleted() || !b.getPlayer().isOnline()) { - this.boards.remove(b.getPlayer()); - } - } - - this.boards.forEach((player, board) -> board.updateLines(this.lines.accept(player))); - - }); - } - - /** - * Create a scoreboard for a player - * - * @param player - * @param title - * @return {@link FastBoard} - */ - public FastBoard createBoard(Player player, String title) { - - if (this.hasBoard(player)) { - return this.getBoard(player); - } - - FastBoard board = new FastBoard(player); - board.updateTitle(title); - - if (this.lines != null) { - board.updateLines(this.lines.accept(player)); - } - - this.boards.put(player, board); - - return board; - - } - - /** - * Delete player board - * - * @param player - * @return - */ - public boolean delete(Player player) { - - if (!this.hasBoard(player)) { - return false; - } - - FastBoard board = getBoard(player); - if (!board.isDeleted()) { - board.delete(); - return true; - } - - return false; - } - - /** - * Update board title - * - * @param player - * @param title - * @return boolean - */ - public boolean updateTitle(Player player, String title) { - - if (!hasBoard(player)) { - return false; - } - - FastBoard board = getBoard(player); - if (!board.isDeleted()) { - board.updateTitle(title); - return true; - } - return false; - } - - public boolean updateLine(Player player, int line, String string) { - - if (!hasBoard(player)) { - return false; - } - - FastBoard board = getBoard(player); - if (!board.isDeleted()) { - board.updateLine(line, string); - return true; - } - return false; - } - - /** - * Check if player has board - * - * @param player - * @return {@link Boolean} - */ - public boolean hasBoard(Player player) { - return this.boards.containsKey(player); - } - - /** - * Return player's board - * - * @param player - * @return {@link FastBoard} - */ - public FastBoard getBoard(Player player) { - return this.boards.getOrDefault(player, null); - } - - /** - * @return the boards - */ - public Map getBoards() { - return this.boards; - } - - /** - * @return the schedulerMillisecond - */ - public long getSchedulerMillisecond() { - return this.schedulerMillisecond; - } - - /** - * @return the isRunning - */ - public boolean isRunning() { - return this.isRunning; - } - - /** - * @param isRunning the isRunning to set - */ - public void setRunning(boolean isRunning) { - this.isRunning = isRunning; - } - - /** - * @return the lines - */ - public CollectionConsumer getLines() { - return this.lines; - } - - /** - * @param lines the lines to set - */ - public void setLines(CollectionConsumer lines) { - this.lines = lines; - } - - /** - * @param lines the lines to set - */ - public void setLinesAndSchedule(CollectionConsumer lines) { - this.lines = lines; - this.schedule(); - } - -} diff --git a/API/src/main/java/fr/maxlego08/items/api/CloneUtils.java b/src/main/java/fr/maxlego08/items/utils/CloneUtils.java similarity index 96% rename from API/src/main/java/fr/maxlego08/items/api/CloneUtils.java rename to src/main/java/fr/maxlego08/items/utils/CloneUtils.java index 5447aff..5f8d0e0 100644 --- a/API/src/main/java/fr/maxlego08/items/api/CloneUtils.java +++ b/src/main/java/fr/maxlego08/items/utils/CloneUtils.java @@ -1,4 +1,4 @@ -package fr.maxlego08.items.api; +package fr.maxlego08.items.utils; import fr.maxlego08.menu.api.dupe.DupeManager; import org.bukkit.Bukkit; diff --git a/src/main/java/fr/maxlego08/items/zcore/ZPlugin.java b/src/main/java/fr/maxlego08/items/zcore/ZPlugin.java index 231a917..249c139 100644 --- a/src/main/java/fr/maxlego08/items/zcore/ZPlugin.java +++ b/src/main/java/fr/maxlego08/items/zcore/ZPlugin.java @@ -3,17 +3,14 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import fr.maxlego08.items.ItemsPlugin; +import fr.maxlego08.items.api.utils.Plugins; import fr.maxlego08.items.command.CommandManager; import fr.maxlego08.items.command.VCommand; -import fr.maxlego08.items.exceptions.ListenerNullException; -import fr.maxlego08.items.listener.AdapterListener; -import fr.maxlego08.items.listener.ListenerAdapter; import fr.maxlego08.items.placeholder.LocalPlaceholder; import fr.maxlego08.items.placeholder.Placeholder; import fr.maxlego08.items.zcore.logger.Logger; import fr.maxlego08.items.zcore.utils.gson.LocationAdapter; import fr.maxlego08.items.zcore.utils.gson.PotionEffectAdapter; -import fr.maxlego08.items.api.utils.Plugins; import fr.maxlego08.items.zcore.utils.storage.NoReloadable; import fr.maxlego08.items.zcore.utils.storage.Persist; import fr.maxlego08.items.zcore.utils.storage.Savable; @@ -37,7 +34,6 @@ public abstract class ZPlugin extends JavaPlugin { public static final ExecutorService service = Executors.newFixedThreadPool(5); private final Logger log = new Logger(this.getDescription().getFullName()); private final List savers = new ArrayList<>(); - private final List listenerAdapters = new ArrayList<>(); protected CommandManager commandManager; private Gson gson; private Persist persist; @@ -59,9 +55,6 @@ protected void preEnable() { this.persist = new Persist(this); this.commandManager = new CommandManager((ItemsPlugin) this); - - /* Add Listener */ - this.addListener(new AdapterListener((ItemsPlugin) this)); } protected void postEnable() { @@ -80,6 +73,23 @@ protected void preDisable() { } protected void postDisable() { + // Shutdown the executor service to prevent thread leaks + if (!service.isShutdown()) { + this.log.log("Shutting down executor service...", Logger.LogType.INFO); + service.shutdown(); + try { + // Wait for tasks to complete for up to 5 seconds + if (!service.awaitTermination(5, java.util.concurrent.TimeUnit.SECONDS)) { + this.log.log("Executor service did not terminate in time, forcing shutdown...", Logger.LogType.WARNING); + service.shutdownNow(); + } + } catch (InterruptedException e) { + this.log.log("Executor service shutdown interrupted", Logger.LogType.ERROR); + service.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + this.log.log("=== DISABLE DONE <&>7(<&>6" + Math.abs(enableTime - System.currentTimeMillis()) + "ms<&>7) <&>e==="); } @@ -103,17 +113,6 @@ public void addListener(Listener listener) { Bukkit.getPluginManager().registerEvents(listener, this); } - /** - * Add a listener from ListenerAdapter - * - * @param adapter - */ - public void addListener(ListenerAdapter adapter) { - if (adapter == null) throw new ListenerNullException("Warning, your listener is null"); - if (adapter instanceof Savable) this.addSave((Savable) adapter); - this.listenerAdapters.add(adapter); - } - /** * Add a Saveable * @@ -167,13 +166,6 @@ protected T getProvider(Class classz) { return provider.getProvider() != null ? provider.getProvider() : null; } - /** - * @return listenerAdapters - */ - public List getListenerAdapters() { - return listenerAdapters; - } - /** * @return the commandManager */ diff --git a/src/main/java/fr/maxlego08/items/zcore/enums/EnumInventory.java b/src/main/java/fr/maxlego08/items/zcore/enums/EnumInventory.java deleted file mode 100644 index 7be3b50..0000000 --- a/src/main/java/fr/maxlego08/items/zcore/enums/EnumInventory.java +++ /dev/null @@ -1,17 +0,0 @@ -package fr.maxlego08.items.zcore.enums; - -public enum EnumInventory { - - ; - - private final int id; - - private EnumInventory(int id) { - this.id = id; - } - - public int getId() { - return id; - } - -} diff --git a/src/main/java/fr/maxlego08/items/zcore/enums/Folder.java b/src/main/java/fr/maxlego08/items/zcore/enums/Folder.java deleted file mode 100644 index ce3d44a..0000000 --- a/src/main/java/fr/maxlego08/items/zcore/enums/Folder.java +++ /dev/null @@ -1,14 +0,0 @@ -package fr.maxlego08.items.zcore.enums; - -public enum Folder { - - UTILS, - - ; - - - public String toFolder(){ - return name().toLowerCase(); - } - -} diff --git a/src/main/java/fr/maxlego08/items/zcore/utils/BarAnimation.java b/src/main/java/fr/maxlego08/items/zcore/utils/BarAnimation.java deleted file mode 100644 index 3af1cdd..0000000 --- a/src/main/java/fr/maxlego08/items/zcore/utils/BarAnimation.java +++ /dev/null @@ -1,62 +0,0 @@ -package fr.maxlego08.items.zcore.utils; - -import org.bukkit.Bukkit; -import org.bukkit.boss.BarColor; -import org.bukkit.boss.BarStyle; -import org.bukkit.boss.BossBar; -import org.bukkit.entity.Player; -import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.scheduler.BukkitRunnable; - -import java.util.List; - -/** - * Creates and manages a smooth BossBar animation for a group of players. - * Extends {@link BukkitRunnable} to handle the timing and updates of the BossBar. - */ -public class BarAnimation extends BukkitRunnable { - - private final BossBar bossBar; - private final double totalTime; - private double remainingTime; - - /** - * Creates a new smooth BossBar animation for a group of players. - * - * @param players the list of players to display the BossBar to. - * @param text the text to display on the BossBar. - * @param seconds the total duration of the animation in seconds. - * @param barColor the color of the BossBar. - * @param barStyle the style of the BossBar. - */ - public BarAnimation(List players, String text, int seconds, BarColor barColor, BarStyle barStyle) { - this.bossBar = Bukkit.createBossBar(text, barColor, barStyle); - this.totalTime = seconds * 20.0; // Convert seconds to ticks (20 ticks = 1 second) - this.remainingTime = totalTime; - - for (Player player : players) { - this.bossBar.addPlayer(player); - } - - this.bossBar.setVisible(true); - // Schedule the task to run every tick (1L) for the smoothest animation - this.runTaskTimer(JavaPlugin.getProvidingPlugin(getClass()), 0L, 1L); - } - - /** - * The task to run on each tick. Updates the BossBar's progress. - * Cancels the task and removes the BossBar when the time runs out. - */ - @Override - public void run() { - double progress = remainingTime / totalTime; - bossBar.setProgress(progress); - - if (remainingTime <= 0) { - bossBar.removeAll(); - this.cancel(); // Stop the task when the BossBar is empty - } - - remainingTime -= 1; // Decrease by 1 tick at each update - } -} diff --git a/src/main/java/fr/maxlego08/items/zcore/utils/MessageUtils.java b/src/main/java/fr/maxlego08/items/zcore/utils/MessageUtils.java index 7871e73..f916eb2 100644 --- a/src/main/java/fr/maxlego08/items/zcore/utils/MessageUtils.java +++ b/src/main/java/fr/maxlego08/items/zcore/utils/MessageUtils.java @@ -1,17 +1,22 @@ package fr.maxlego08.items.zcore.utils; +import fr.maxlego08.items.ItemsPlugin; +import fr.maxlego08.items.api.ItemComponent; +import fr.maxlego08.items.api.ItemPlugin; +import fr.maxlego08.items.zcore.ZPlugin; import fr.maxlego08.items.zcore.enums.Message; import fr.maxlego08.items.zcore.enums.MessageType; import fr.maxlego08.items.zcore.utils.nms.NmsVersion; -import fr.maxlego08.items.zcore.utils.players.ActionBar; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.bukkit.command.ConsoleCommandSender; import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; import java.lang.reflect.Constructor; import java.util.List; +import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -26,28 +31,6 @@ public abstract class MessageUtils extends LocationUtils { private final static int CENTER_PX = 154; - /** - * Sends a message without prefix to the specified command sender. - * - * @param player the command sender to send the message to. - * @param message the message to send. - * @param args the arguments for the message. - */ - protected void messageWO(CommandSender player, Message message, Object... args) { - player.sendMessage(getMessage(message, args)); - } - - /** - * Sends a message without prefix to the specified command sender. - * - * @param player the command sender to send the message to. - * @param message the message to send. - * @param args the arguments for the message. - */ - protected void messageWO(CommandSender player, String message, Object... args) { - player.sendMessage(getMessage(message, args)); - } - /** * Sends a message with prefix to the specified command sender. * @@ -56,18 +39,10 @@ protected void messageWO(CommandSender player, String message, Object... args) { * @param args the arguments for the message. */ protected void message(CommandSender sender, String message, Object... args) { - sender.sendMessage(Message.PREFIX.msg() + getMessage(message, args)); + ItemsPlugin plugin = JavaPlugin.getPlugin(ItemsPlugin.class); + plugin.getItemComponent().sendMessage(sender, Message.PREFIX.msg() + getMessage(message, args)); } - /** - * Sends a message to the specified command sender. - * - * @param sender the command sender to send the message to. - * @param message the message to send. - */ - private void message(CommandSender sender, String message) { - sender.sendMessage(color(message)); - } /** * Sends a chat message to the specified player. @@ -91,7 +66,8 @@ private void sendTchatMessage(Player player, Message message, Object... args) { * @param message the message - using the Message enum for simplified message management. * @param args the arguments - the arguments work in pairs, you must put for example %test% and then the value. */ - protected void message(CommandSender sender, Message message, Object... args) { + public void message(CommandSender sender, Message message, Object... args) { + ItemsPlugin plugin = JavaPlugin.getPlugin(ItemsPlugin.class); if (sender instanceof ConsoleCommandSender) { if (message.getMessages().size() > 0) { message.getMessages().forEach(msg -> message(sender, getMessage(msg, args))); @@ -109,10 +85,10 @@ protected void message(CommandSender sender, Message message, Object... args) { } break; case ACTION: - this.actionMessage(player, message, args); + plugin.getItemComponent().sendActionBar(player, getMessage(message, args)); break; case TCHAT_AND_ACTION: - this.actionMessage(player, message, args); + plugin.getItemComponent().sendActionBar(player, getMessage(message, args)); sendTchatMessage(player, message, args); break; case TCHAT: @@ -125,7 +101,7 @@ protected void message(CommandSender sender, Message message, Object... args) { int fadeInTime = message.getStart(); int showTime = message.getTime(); int fadeOutTime = message.getEnd(); - this.title(player, this.papi(this.getMessage(title, args), player), this.papi(this.getMessage(subTitle, args), player), fadeInTime, showTime, fadeOutTime); + plugin.getItemComponent().sendTitle(player, this.papi(this.getMessage(title, args), player), this.papi(this.getMessage(subTitle, args), player), fadeInTime, showTime, fadeOutTime); break; default: break; @@ -133,30 +109,6 @@ protected void message(CommandSender sender, Message message, Object... args) { } } - /** - * Broadcasts a message to all online players and the console. - * - * @param message the message to broadcast. - * @param args the arguments for the message. - */ - protected void broadcast(Message message, Object... args) { - for (Player player : Bukkit.getOnlinePlayers()) { - message(player, message, args); - } - message(Bukkit.getConsoleSender(), message, args); - } - - /** - * Sends an action bar message to the specified player. - * - * @param player the player to send the message to. - * @param message the message to send. - * @param args the arguments for the message. - */ - protected void actionMessage(Player player, Message message, Object... args) { - ActionBar.sendActionBar(player, color(this.papi(getMessage(message, args), player))); - } - /** * Gets the formatted message with arguments replaced. * @@ -164,7 +116,7 @@ protected void actionMessage(Player player, Message message, Object... args) { * @param args the arguments for the message. * @return the formatted message. */ - protected String getMessage(Message message, Object... args) { + public static String getMessage(Message message, Object... args) { return getMessage(message.getMessage(), args); } @@ -175,7 +127,7 @@ protected String getMessage(Message message, Object... args) { * @param args the arguments for the message. * @return the formatted message. */ - protected String getMessage(String message, Object... args) { + public static String getMessage(String message, Object... args) { if (args.length % 2 != 0) { throw new IllegalArgumentException("Number of invalid arguments. Arguments must be in pairs."); } @@ -189,69 +141,6 @@ protected String getMessage(String message, Object... args) { return message; } - /** - * Gets a class from the net.minecraft.server package. - * - * @param name the name of the class. - * @return the class object, or null if not found. - */ - protected final Class getNMSClass(String name) { - try { - return Class.forName("net.minecraft.server." + Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3] + "." + name); - } catch (ClassNotFoundException e) { - e.printStackTrace(); - } - return null; - } - - /** - * Sends a title to the player. - * - * @param player the player to send the title to. - * @param title the title text. - * @param subtitle the subtitle text. - * @param fadeInTime the fade-in time in ticks. - * @param showTime the showtime in ticks. - * @param fadeOutTime the fade-out time in ticks. - */ - protected void title(Player player, String title, String subtitle, int fadeInTime, int showTime, int fadeOutTime) { - if (NmsVersion.nmsVersion.isNewMaterial()) { - player.sendTitle(title, subtitle, fadeInTime, showTime, fadeOutTime); - return; - } - - try { - Object chatTitle = getNMSClass("IChatBaseComponent").getDeclaredClasses()[0].getMethod("a", String.class).invoke(null, "{\"text\": \"" + title + "\"}"); - Constructor titleConstructor = getNMSClass("PacketPlayOutTitle").getConstructor(getNMSClass("PacketPlayOutTitle").getDeclaredClasses()[0], getNMSClass("IChatBaseComponent"), int.class, int.class, int.class); - Object packet = titleConstructor.newInstance(getNMSClass("PacketPlayOutTitle").getDeclaredClasses()[0].getField("TITLE").get(null), chatTitle, fadeInTime, showTime, fadeOutTime); - - Object chatsTitle = getNMSClass("IChatBaseComponent").getDeclaredClasses()[0].getMethod("a", String.class).invoke(null, "{\"text\": \"" + subtitle + "\"}"); - Constructor timingTitleConstructor = getNMSClass("PacketPlayOutTitle").getConstructor(getNMSClass("PacketPlayOutTitle").getDeclaredClasses()[0], getNMSClass("IChatBaseComponent"), int.class, int.class, int.class); - Object timingPacket = timingTitleConstructor.newInstance(getNMSClass("PacketPlayOutTitle").getDeclaredClasses()[0].getField("SUBTITLE").get(null), chatsTitle, fadeInTime, showTime, fadeOutTime); - - sendPacket(player, packet); - sendPacket(player, timingPacket); - } catch (Exception e) { - e.printStackTrace(); - } - } - - /** - * Sends a packet to the player. - * - * @param player the player to send the packet to. - * @param packet the packet to send. - */ - protected final void sendPacket(Player player, Object packet) { - try { - Object handle = player.getClass().getMethod("getHandle").invoke(player); - Object playerConnection = handle.getClass().getField("playerConnection").get(handle); - playerConnection.getClass().getMethod("sendPacket", getNMSClass("Packet")).invoke(playerConnection, packet); - } catch (Exception e) { - e.printStackTrace(); - } - } - /** * Gets a centered message. * @@ -293,49 +182,4 @@ protected String getCenteredMessage(String message) { return sb + message; } - /** - * Broadcasts a centered message to all online players. - * - * @param messages the list of messages to broadcast. - */ - protected void broadcastCenterMessage(List messages) { - messages.stream().map(this::getCenteredMessage).forEach(e -> { - for (Player player : Bukkit.getOnlinePlayers()) { - messageWO(player, e); - } - }); - } - - /** - * Broadcasts an action bar message to all online players. - * - * @param message the message to broadcast. - */ - protected void broadcastAction(String message) { - for (Player player : Bukkit.getOnlinePlayers()) { - ActionBar.sendActionBar(player, papi(message, player)); - } - } - - /** - * Translates alternate color codes in the message string. - * - * @param message the message to color. - * @return the colored message. - */ - protected String color(String message) { - if (message == null) { - return null; - } - if (NmsVersion.nmsVersion.isHexVersion()) { - Pattern pattern = Pattern.compile("#[a-fA-F0-9]{6}"); - Matcher matcher = pattern.matcher(message); - while (matcher.find()) { - String color = message.substring(matcher.start(), matcher.end()); - message = message.replace(color, String.valueOf(net.md_5.bungee.api.ChatColor.of(color))); - matcher = pattern.matcher(message); - } - } - return net.md_5.bungee.api.ChatColor.translateAlternateColorCodes('&', message); - } } diff --git a/src/main/java/fr/maxlego08/items/zcore/utils/PapiUtils.java b/src/main/java/fr/maxlego08/items/zcore/utils/PapiUtils.java index 4444477..69feef3 100644 --- a/src/main/java/fr/maxlego08/items/zcore/utils/PapiUtils.java +++ b/src/main/java/fr/maxlego08/items/zcore/utils/PapiUtils.java @@ -9,32 +9,6 @@ public class PapiUtils { - /** - * Applies PlaceholderAPI transformations to the display name and lore of an ItemStack. - * - * @param itemStack the ItemStack to transform. - * @param player the player context for the placeholders. - * @return the transformed ItemStack. - */ - protected ItemStack papi(ItemStack itemStack, Player player) { - if (itemStack == null) { - return itemStack; - } - - ItemMeta itemMeta = itemStack.getItemMeta(); - - if (itemMeta.hasDisplayName()) { - itemMeta.setDisplayName(Placeholder.getPlaceholder().setPlaceholders(player, itemMeta.getDisplayName())); - } - - if (itemMeta.hasLore()) { - itemMeta.setLore(Placeholder.getPlaceholder().setPlaceholders(player, itemMeta.getLore())); - } - - itemStack.setItemMeta(itemMeta); - return itemStack; - } - /** * Applies PlaceholderAPI transformations to a string. * diff --git a/src/main/java/fr/maxlego08/items/zcore/utils/PlayerSkin.java b/src/main/java/fr/maxlego08/items/zcore/utils/PlayerSkin.java deleted file mode 100644 index 1da33cc..0000000 --- a/src/main/java/fr/maxlego08/items/zcore/utils/PlayerSkin.java +++ /dev/null @@ -1,151 +0,0 @@ -package fr.maxlego08.items.zcore.utils; - -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.mojang.authlib.GameProfile; -import com.mojang.authlib.properties.Property; -import fr.maxlego08.items.zcore.utils.nms.NmsVersion; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.io.IOException; -import java.io.InputStreamReader; -import java.lang.reflect.InvocationTargetException; -import java.net.URL; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -/** - * Utility class for managing player skins, including fetching textures and signatures. - * Based on https://www.spigotmc.org/threads/how-to-get-a-players-texture.244966/ - */ -public class PlayerSkin { - - private static final Map textures = new HashMap<>(); - private static final ExecutorService pool = Executors.newCachedThreadPool(); - - /** - * Gets the texture of a player. - * - * @param player the player whose texture is to be retrieved. - * @return the texture of the player, or null if it cannot be retrieved. - */ - public static String getTexture(Player player) { - if (textures.containsKey(player.getName())) { - return textures.get(player.getName()); - } - String[] textures = getFromPlayer(player); - try { - String texture = textures[0]; - PlayerSkin.textures.put(player.getName(), texture); - return texture; - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } - - /** - * Gets the texture of a player by their name. - * - * @param name the name of the player whose texture is to be retrieved. - * @return the texture of the player, or null if it cannot be retrieved. - */ - public static String getTexture(String name) { - if (textures.containsKey(name)) { - return textures.get(name); - } - - Player player = Bukkit.getPlayer(name); - if (player != null) { - return getTexture(player); - } - - pool.execute(() -> { - String[] textures = getFromName(name); - try { - String texture = textures[0]; - PlayerSkin.textures.put(name, texture); - } catch (Exception e) { - e.printStackTrace(); - } - }); - return null; - } - - /** - * Gets the texture and signature of a player from their player object. - * - * @param playerBukkit the player whose texture and signature are to be retrieved. - * @return an array containing the texture and signature of the player. - */ - public static String[] getFromPlayer(Player playerBukkit) { - GameProfile profile = getProfile(playerBukkit); - Property property = profile.getProperties().get("textures").iterator().next(); - String texture = property.getValue(); - String signature = property.getSignature(); - - return new String[]{texture, signature}; - } - - /** - * Gets the texture and signature of a player from their name. - * - * @param name the name of the player whose texture and signature are to be retrieved. - * @return an array containing the texture and signature of the player. - */ - @SuppressWarnings("deprecation") - public static String[] getFromName(String name) { - try { - URL url_0 = new URL("https://api.mojang.com/users/profiles/minecraft/" + name); - InputStreamReader reader_0 = new InputStreamReader(url_0.openStream()); - String uuid = new JsonParser().parse(reader_0).getAsJsonObject().get("id").getAsString(); - - URL url_1 = new URL("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid + "?unsigned=false"); - InputStreamReader reader_1 = new InputStreamReader(url_1.openStream()); - JsonObject textureProperty = new JsonParser().parse(reader_1).getAsJsonObject().get("properties") - .getAsJsonArray().get(0).getAsJsonObject(); - String texture = textureProperty.get("value").getAsString(); - String signature = textureProperty.get("signature").getAsString(); - - return new String[]{texture, signature}; - } catch (IOException e) { - System.err.println("Could not get skin data from session servers!"); - e.printStackTrace(); - return null; - } - } - - /** - * Gets the GameProfile of a player. - * - * @param player the player whose GameProfile is to be retrieved. - * @return the GameProfile of the player. - */ - public static GameProfile getProfile(Player player) { - try { - Object entityPlayer = player.getClass().getMethod("getHandle").invoke(player); - return (GameProfile) entityPlayer.getClass().getMethod(getMethodName()).invoke(entityPlayer); - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { - e.printStackTrace(); - } - return null; - } - - /** - * Gets the method name for retrieving the GameProfile based on the server version. - * - * @return the method name for retrieving the GameProfile. - */ - public static String getMethodName() { - double nmsVersion = NmsVersion.nmsVersion.getVersion(); - if (nmsVersion >= NmsVersion.V_1_19.getVersion() && nmsVersion <= NmsVersion.V_1_19_2.getVersion()) { - return "fz"; - } else if (nmsVersion >= NmsVersion.V_1_18.getVersion() && nmsVersion <= NmsVersion.V_1_18_2.getVersion()) { - return "fp"; - } - return "getProfile"; - } -} diff --git a/src/main/java/fr/maxlego08/items/zcore/utils/ProgressBar.java b/src/main/java/fr/maxlego08/items/zcore/utils/ProgressBar.java deleted file mode 100644 index 30b26a6..0000000 --- a/src/main/java/fr/maxlego08/items/zcore/utils/ProgressBar.java +++ /dev/null @@ -1,64 +0,0 @@ -package fr.maxlego08.items.zcore.utils; - -/** - * This class represents a progress bar with customizable length, symbol, and colors for completed and not completed parts. - */ -public class ProgressBar { - - private final int length; - private final char symbol; - private final String completedColor; - private final String notCompletedColor; - - /** - * Constructs a new ProgressBar with the specified attributes. - * - * @param length the total length of the progress bar. - * @param symbol the symbol used to represent the progress. - * @param completedColor the color used for the completed portion of the bar. - * @param notCompletedColor the color used for the not completed portion of the bar. - */ - public ProgressBar(int length, char symbol, String completedColor, String notCompletedColor) { - super(); - this.length = length; - this.symbol = symbol; - this.completedColor = completedColor; - this.notCompletedColor = notCompletedColor; - } - - /** - * Gets the total length of the progress bar. - * - * @return the length of the progress bar. - */ - public int getLength() { - return length; - } - - /** - * Gets the symbol used to represent the progress. - * - * @return the symbol of the progress bar. - */ - public char getSymbol() { - return symbol; - } - - /** - * Gets the color used for the completed portion of the bar. - * - * @return the completed color as a string. - */ - public String getCompletedColor() { - return completedColor; - } - - /** - * Gets the color used for the not completed portion of the bar. - * - * @return the not completed color as a string. - */ - public String getNotCompletedColor() { - return notCompletedColor; - } -} diff --git a/src/main/java/fr/maxlego08/items/zcore/utils/RandomString.java b/src/main/java/fr/maxlego08/items/zcore/utils/RandomString.java deleted file mode 100644 index f40826a..0000000 --- a/src/main/java/fr/maxlego08/items/zcore/utils/RandomString.java +++ /dev/null @@ -1,79 +0,0 @@ -package fr.maxlego08.items.zcore.utils; - -import java.security.SecureRandom; -import java.util.Locale; -import java.util.Objects; -import java.util.Random; - -/** - * Utility class for generating random strings. - * Supports generating alphanumeric strings using different random generators and symbol sets. - */ -public class RandomString { - - /** - * Generates a random string. - * - * @return a randomly generated string. - */ - public String nextString() { - for (int idx = 0; idx < buf.length; ++idx) { - buf[idx] = symbols[random.nextInt(symbols.length)]; - } - return new String(buf); - } - - public static final String upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - public static final String lower = upper.toLowerCase(Locale.ROOT); - public static final String digits = "0123456789"; - public static final String alphanum = upper + lower + digits; - - private final Random random; - private final char[] symbols; - private final char[] buf; - - /** - * Constructs a new RandomString with the specified length, random generator, and symbol set. - * - * @param length the length of the generated strings. - * @param random the random generator to use. - * @param symbols the set of symbols to use for generating the string. - * @throws IllegalArgumentException if length is less than 1 or symbols length is less than 2. - */ - public RandomString(int length, Random random, String symbols) { - if (length < 1) throw new IllegalArgumentException(); - if (symbols.length() < 2) throw new IllegalArgumentException(); - this.random = Objects.requireNonNull(random); - this.symbols = symbols.toCharArray(); - this.buf = new char[length]; - } - - /** - * Constructs a new RandomString with the specified length and random generator. - * Uses the alphanumeric symbol set. - * - * @param length the length of the generated strings. - * @param random the random generator to use. - */ - public RandomString(int length, Random random) { - this(length, random, alphanum); - } - - /** - * Constructs a new RandomString with the specified length. - * Uses a secure random generator and the alphanumeric symbol set. - * - * @param length the length of the generated strings. - */ - public RandomString(int length) { - this(length, new SecureRandom()); - } - - /** - * Constructs a new RandomString with a default length of 21. - * Uses a secure random generator and the alphanumeric symbol set. - */ - public RandomString() { - this(21); - } -} diff --git a/src/main/java/fr/maxlego08/items/zcore/utils/ZUtils.java b/src/main/java/fr/maxlego08/items/zcore/utils/ZUtils.java index ea1287e..e959e2d 100644 --- a/src/main/java/fr/maxlego08/items/zcore/utils/ZUtils.java +++ b/src/main/java/fr/maxlego08/items/zcore/utils/ZUtils.java @@ -1,52 +1,29 @@ package fr.maxlego08.items.zcore.utils; -import com.google.common.base.Strings; -import com.mojang.authlib.GameProfile; -import com.mojang.authlib.properties.Property; -import fr.maxlego08.items.ItemsPlugin; -import fr.maxlego08.items.zcore.enums.EnumInventory; -import fr.maxlego08.items.zcore.enums.Permission; -import fr.maxlego08.items.zcore.utils.builder.CooldownBuilder; -import fr.maxlego08.items.zcore.utils.builder.TimerBuilder; -import fr.maxlego08.items.zcore.utils.nms.ItemStackUtils; +import fr.maxlego08.items.zcore.ZPlugin; import fr.maxlego08.items.zcore.utils.nms.NmsVersion; -import fr.maxlego08.items.zcore.utils.players.ActionBar; -import net.md_5.bungee.api.chat.BaseComponent; -import net.md_5.bungee.api.chat.ClickEvent; -import net.md_5.bungee.api.chat.HoverEvent; -import net.md_5.bungee.api.chat.TextComponent; -import org.bukkit.*; -import org.bukkit.block.BlockFace; -import org.bukkit.command.Command; -import org.bukkit.command.PluginCommand; -import org.bukkit.command.SimpleCommandMap; -import org.bukkit.enchantments.Enchantment; +import org.bukkit.Material; import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.inventory.meta.SkullMeta; import org.bukkit.permissions.Permissible; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.RegisteredServiceProvider; -import org.bukkit.potion.PotionEffectType; -import java.lang.reflect.Field; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.NumberFormat; -import java.util.*; -import java.util.concurrent.ThreadLocalRandom; -import java.util.function.BiConsumer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; -import java.util.stream.Stream; @SuppressWarnings("deprecation") public abstract class ZUtils extends MessageUtils { + private static final Pattern HEX_COLOR_PATTERN = Pattern.compile(net.md_5.bungee.api.ChatColor.COLOR_CHAR + "x[a-fA-F0-9-" + net.md_5.bungee.api.ChatColor.COLOR_CHAR + "]{12}"); + // For plugin support from 1.8 to 1.12 private static Material[] byId; @@ -62,37 +39,6 @@ public abstract class ZUtils extends MessageUtils { } } - /** - * Encodes an ItemStack to a base64 string. - * - * @param item the ItemStack to encode. - * @return the encoded item as a base64 string. - */ - protected String encode(ItemStack item) { - return ItemStackUtils.serializeItemStack(item); - } - - /** - * Decodes a base64 string to an ItemStack. - * - * @param item the encoded ItemStack as a base64 string. - * @return the decoded ItemStack. - */ - protected ItemStack decode(String item) { - return ItemStackUtils.deserializeItemStack(item); - } - - /** - * Gets a random number between the specified bounds. - * - * @param min the lower bound (inclusive). - * @param max the upper bound (exclusive). - * @return a random number between a and b. - */ - protected int getNumberBetween(int min, int max) { - return ThreadLocalRandom.current().nextInt(min, max); - } - /** * Checks if the player's inventory is full. * @@ -110,696 +56,14 @@ protected boolean hasInventoryFull(Player player) { } - /** - * Gives an item to the player. If the player's inventory is full, the item will be dropped on the ground. - * - * @param player the player to receive the item. - * @param item the item to give to the player. - */ - protected void give(Player player, ItemStack item) { - if (hasInventoryFull(player)) { - player.getWorld().dropItem(player.getLocation(), item); - } else { - player.getInventory().addItem(item); - } - } - - /** - * Gets a Material based on its ID. Works only for plugins from versions 1.8 to 1.12. - * - * @param id the ID of the material. - * @return the Material corresponding to the ID, or Material.AIR if the ID is invalid. - */ - protected Material getMaterial(int id) { - return byId.length > id && id >= 0 ? byId[id] : Material.AIR; - } - - /** - * Checks if an ItemStack has a display name. - * - * @param itemStack the ItemStack to check. - * @return true if the ItemStack has a display name, false otherwise. - */ - protected boolean hasDisplayName(ItemStack itemStack) { - return itemStack.hasItemMeta() && itemStack.getItemMeta().hasDisplayName(); - } - - /** - * Checks if the name of an ItemStack is the same as the given string. - * - * @param itemStack the ItemStack to check. - * @param name the string to compare with the ItemStack's name. - * @return true if the ItemStack's name is the same as the given string, false otherwise. - */ - protected boolean same(ItemStack itemStack, String name) { - return this.hasDisplayName(itemStack) && itemStack.getItemMeta().getDisplayName().equals(name); - } - - /** - * Checks if the name of an ItemStack contains the given string. - * - * @param itemStack the ItemStack to check. - * @param name the string to check if it is contained in the ItemStack's name. - * @return true if the ItemStack's name contains the given string, false otherwise. - */ - protected boolean contains(ItemStack itemStack, String name) { - return this.hasDisplayName(itemStack) && itemStack.getItemMeta().getDisplayName().contains(name); - } - - /** - * Removes a specified number of items from the player's hand. If the amount to be removed is not specified, it defaults to 64. - * - * @param player the player from whose hand the items will be removed. - */ - protected void removeItemInHand(Player player) { - removeItemInHand(player, 64); - } - - /** - * Removes a specified number of items from the player's hand. - * - * @param player the player from whose hand the items will be removed. - * @param how the number of items to remove. - */ - protected void removeItemInHand(Player player, int how) { - if (player.getItemInHand().getAmount() > how) { - player.getItemInHand().setAmount(player.getItemInHand().getAmount() - how); - } else { - player.setItemInHand(new ItemStack(Material.AIR)); - } - player.updateInventory(); - } - - /** - * Checks if two locations are identical. - * - * @param firstLocation the first location. - * @param secondLocation the second location. - * @return true if both locations are the same, false otherwise. - */ - protected boolean same(Location firstLocation, Location secondLocation) { - return (firstLocation.getBlockX() == secondLocation.getBlockX()) && (firstLocation.getBlockY() == secondLocation.getBlockY()) && (firstLocation.getBlockZ() == secondLocation.getBlockZ()) && firstLocation.getWorld().getName().equals(secondLocation.getWorld().getName()); + public void runAsync(ZPlugin plugin, Runnable runnable) { + plugin.getServer().getScheduler().runTaskAsynchronously(plugin, runnable); } - /** - * Formats a double value into a string with two decimal places. - * - * @param decimal the double value to format. - * @return the formatted string. - */ - protected String format(double decimal) { - return format(decimal, "#.##"); - } - - - /** - * Formats a double value into a string according to a specified format. - * - * @param decimal the double value to format. - * @param format the format to apply. - * @return the formatted string. - */ - protected String format(double decimal, String format) { - DecimalFormat decimalFormat = new DecimalFormat(format); - return decimalFormat.format(decimal); - } - - /** - * Removes a specified number of items from a player's inventory. - * - * @param player the player whose items will be removed. - * @param amount the number of items to remove. - * @param itemStack the ItemStack to be removed. - */ - protected void removeItems(Player player, int amount, ItemStack itemStack) { - int slot = 0; - for (ItemStack is : player.getInventory().getContents()) { - if (is != null && is.isSimilar(itemStack) && amount > 0) { - int currentAmount = is.getAmount() - amount; - amount -= is.getAmount(); - if (currentAmount <= 0) { - if (slot == 40) { - player.getInventory().setItemInOffHand(null); - } else { - player.getInventory().removeItem(is); - } - } else { - is.setAmount(currentAmount); - } - } - slot++; - } - player.updateInventory(); - } - - /** - * Schedules a task to be run after a specified delay. - * - * @param delay the delay in milliseconds before the task is executed. - * @param runnable the task to be executed. - */ - protected void schedule(long delay, Runnable runnable) { - new Timer().schedule(new TimerTask() { - @Override - public void run() { - if (runnable != null) runnable.run(); - } - }, delay); - } - - /** - * Formats a string by replacing underscores with spaces and capitalizing the first letter. - * - * @param string the string to format. - * @return the formatted string. - */ - protected String name(String string) { - String name = string.replace("_", " ").toLowerCase(); - return name.substring(0, 1).toUpperCase() + name.substring(1); - } - - /** - * Formats a Material name by replacing underscores with spaces and capitalizing the first letter. - * - * @param string the Material to format. - * @return the formatted string. - */ - protected String name(Material string) { - String name = string.name().replace("_", " ").toLowerCase(); - return name.substring(0, 1).toUpperCase() + name.substring(1); - } - - /** - * Calculates the maximum number of pages needed to display a collection of items, assuming 45 items per page. - * - * @param items the collection of items. - * @return the maximum number of pages. - */ - protected int getMaxPage(Collection items) { - return (items.size() / 45) + 1; - } - - /** - * Calculates the maximum number of pages needed to display a collection of items, with a specified number of items per page. - * - * @param items the collection of items. - * @param a the number of items per page. - * @return the maximum number of pages. - */ - protected int getMaxPage(Collection items, int a) { - return (items.size() / a) + 1; - } - - /** - * Calculates the percentage of a value relative to a total. - * - * @param value the value. - * @param total the total. - * @return the percentage of the value relative to the total. - */ - protected double percent(double value, double total) { - return (value * 100) / total; - } - - /** - * Calculates the numerical value of a percentage of a total. - * - * @param total the total. - * @param percent the percentage. - * @return the numerical value of the percentage of the total. - */ - protected double percentNum(double total, double percent) { - return total * (percent / 100); - } - - /** - * Schedules a repeated task with a specified delay and count. - * - * @param plugin the plugin instance. - * @param delay the delay in milliseconds between each execution. - * @param count the number of times to execute the task. - * @param runnable the task to be executed. - */ - protected void schedule(Plugin plugin, long delay, int count, Runnable runnable) { - new Timer().scheduleAtFixedRate(new TimerTask() { - int tmpCount = 0; - - @Override - public void run() { - if (!plugin.isEnabled()) { - this.cancel(); - return; - } - - if (tmpCount > count) { - this.cancel(); - return; - } - - tmpCount++; - Bukkit.getScheduler().runTask(plugin, runnable); - } - }, 0, delay); - } - - /** - * Checks if a permissible entity has a specific permission. - * - * @param permissible the entity to check. - * @param permission the permission to check for. - * @return true if the entity has the permission, false otherwise. - */ - protected boolean hasPermission(Permissible permissible, Permission permission) { - return permissible.hasPermission(permission.getPermission()); - } - - /** - * Checks if a permissible entity has a specific permission. - * - * @param permissible the entity to check. - * @param permission the permission string to check for. - * @return true if the entity has the permission, false otherwise. - */ - protected boolean hasPermission(Permissible permissible, String permission) { - return permissible.hasPermission(permission); - } - - /** - * Schedules a fixed-rate task with a delay. - * - * @param plugin the plugin instance. - * @param delay the delay in milliseconds. - * @param consumer the consumer to execute with the task and status. - * @return the scheduled {@link TimerTask}. - */ - protected TimerTask scheduleFix(Plugin plugin, long delay, BiConsumer consumer) { - return this.scheduleFix(plugin, delay, delay, consumer); - } - - /** - * Schedules a fixed-rate task with a start delay and a subsequent delay. - * - * @param plugin the plugin instance. - * @param startAt the initial delay in milliseconds. - * @param delay the subsequent delay in milliseconds. - * @param consumer the consumer to execute with the task and status. - * @return the scheduled {@link TimerTask}. - */ - protected TimerTask scheduleFix(Plugin plugin, long startAt, long delay, BiConsumer consumer) { - TimerTask task = new TimerTask() { - @Override - public void run() { - if (!plugin.isEnabled()) { - cancel(); - consumer.accept(this, false); - return; - } - Bukkit.getScheduler().runTask(plugin, () -> consumer.accept(this, true)); - } - }; - new Timer().scheduleAtFixedRate(task, startAt, delay); - return task; - } - - /** - * Gets a random element from a list. - * - * @param elements the list of elements to choose from. - * @param the type of elements in the list. - * @return a random element from the list, or null if the list is empty. - */ - protected T randomElement(List elements) { - if (elements.size() == 0) { - return null; - } - if (elements.size() == 1) { - return elements.get(0); - } - Random random = new Random(); - return elements.get(random.nextInt(elements.size())); - } - - /** - * Reverses color codes in a message string. - * - * @param message the message string with color codes to reverse. - * @return the message string with reversed color codes. - */ - protected String colorReverse(String message) { - Pattern pattern = Pattern.compile(net.md_5.bungee.api.ChatColor.COLOR_CHAR + "x[a-fA-F0-9-" + net.md_5.bungee.api.ChatColor.COLOR_CHAR + "]{12}"); - Matcher matcher = pattern.matcher(message); - while (matcher.find()) { - String color = message.substring(matcher.start(), matcher.end()); - String colorReplace = color.replace("§x", "#"); - colorReplace = colorReplace.replace("§", ""); - message = message.replace(color, colorReplace); - matcher = pattern.matcher(message); - } - - return message == null ? null : message.replace("§", "&"); - } - - /** - * Converts a list of messages to include color codes. - * - * @param messages the list of messages to color. - * @return the list of colored messages. - */ - protected List color(List messages) { - return messages.stream().map(this::color).collect(Collectors.toList()); - } - - /** - * Reverses color codes in a list of messages. - * - * @param messages the list of messages with color codes to reverse. - * @return the list of messages with reversed color codes. - */ protected List colorReverse(List messages) { return messages.stream().map(this::colorReverse).collect(Collectors.toList()); } - - /** - * Gets an ItemFlag from a string representation. - * - * @param flagString the string representation of the ItemFlag. - * @return the corresponding {@link ItemFlag}, or null if not found. - */ - protected ItemFlag getFlag(String flagString) { - for (ItemFlag flag : ItemFlag.values()) { - if (flag.name().equalsIgnoreCase(flagString)) return flag; - } - return null; - } - - /** - * Reverses the order of elements in a list. - * - * @param list the list to reverse. - * @param the type of elements in the list. - * @return a new list with elements in reverse order. - */ - protected List reverse(List list) { - List tmpList = new ArrayList<>(); - for (int index = list.size() - 1; index != -1; index--) { - tmpList.add(list.get(index)); - } - return tmpList; - } - - /** - * Formats a price with commas as thousand separators. - * - * @param price the price to format. - * @return the formatted price string. - */ - protected String price(long price) { - return String.format("%,d", price); - } - - /** - * Generates a random string of specified length. - * - * @param length the length of the random string. - * @return the generated random string. - */ - protected String generateRandomString(int length) { - RandomString randomString = new RandomString(length); - return randomString.nextString(); - } - - /** - * Builds a TextComponent from a message string. - * - * @param message the message string. - * @return the created {@link TextComponent}. - */ - protected TextComponent buildTextComponent(String message) { - return new TextComponent(message); - } - - /** - * Sets a hover message for a TextComponent. - * - * @param component the TextComponent to set the hover message for. - * @param messages the hover messages. - * @return the TextComponent with the hover message set. - */ - protected TextComponent setHoverMessage(TextComponent component, String... messages) { - BaseComponent[] list = new BaseComponent[messages.length]; - for (int a = 0; a != messages.length; a++) { - list[a] = new TextComponent(messages[a] + (messages.length - 1 == a ? "" : "\n")); - } - component.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, list)); - return component; - } - - /** - * Sets a hover message for a TextComponent. - * - * @param component the TextComponent to set the hover message for. - * @param messages the hover messages. - * @return the TextComponent with the hover message set. - */ - protected TextComponent setHoverMessage(TextComponent component, List messages) { - BaseComponent[] list = new BaseComponent[messages.size()]; - for (int a = 0; a != messages.size(); a++) { - list[a] = new TextComponent(messages.get(a) + (messages.size() - 1 == a ? "" : "\n")); - } - component.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, list)); - return component; - } - - /** - * Sets a click action for a TextComponent. - * - * @param component the TextComponent to set the click action for. - * @param action the click action. - * @param command the command to execute on click. - * @return the TextComponent with the click action set. - */ - protected TextComponent setClickAction(TextComponent component, net.md_5.bungee.api.chat.ClickEvent.Action action, String command) { - component.setClickEvent(new ClickEvent(action, command)); - return component; - } - - /** - * Formats a balance value for display. - * - * @param value the balance value. - * @return the formatted balance string. - */ - protected String getDisplayBalance(double value) { - if (value < 10000) return format(value, "#.#"); - else if (value < 1000000) return (int) (value / 1000) + "k "; - else if (value < 1000000000) return format((value / 1000) / 1000, "#.#") + "m "; - else if (value < 1000000000000L) return (int) (((value / 1000) / 1000) / 1000) + "M "; - else return "too much"; - } - - /** - * Formats a balance value for display. - * - * @param value the balance value. - * @return the formatted balance string. - */ - protected String getDisplayBalance(long value) { - if (value < 10000) return format(value, "#.#"); - else if (value < 1000000) return (int) (value / 1000) + "k "; - else if (value < 1000000000) return format((double) (value / 1000) / 1000, "#.#") + "m "; - else if (value < 1000000000000L) return (int) (((value / 1000) / 1000) / 1000) + "M "; - else return "too much"; - } - - /** - * Counts the number of items of a specific material in an inventory. - * - * @param inventory the inventory to check. - * @param material the material to count. - * @return the number of items of the specified material in the inventory. - */ - protected int count(org.bukkit.inventory.Inventory inventory, Material material) { - int count = 0; - for (ItemStack itemStack : inventory.getContents()) { - if (itemStack != null && itemStack.getType().equals(material)) { - count += itemStack.getAmount(); - } - } - return count; - } - - /** - * Gets an Enchantment from a string representation. - * - * @param str the string representation of the Enchantment. - * @return the corresponding {@link Enchantment}, or null if not found. - */ - protected Enchantment enchantFromString(String str) { - for (Enchantment enchantment : Enchantment.values()) { - if (enchantment.getName().equalsIgnoreCase(str)) return enchantment; - } - return null; - } - - - /** - * Gets the closest BlockFace based on a given direction. - * - * @param direction the direction in degrees. - * @return the closest {@link BlockFace}. - */ - protected BlockFace getClosestFace(float direction) { - direction = direction % 360; - - if (direction < 0) direction += 360; - - direction = Math.round(direction / 45); - - switch ((int) direction) { - case 1: - return BlockFace.NORTH_WEST; - case 2: - return BlockFace.NORTH; - case 3: - return BlockFace.NORTH_EAST; - case 4: - return BlockFace.EAST; - case 5: - return BlockFace.SOUTH_EAST; - case 6: - return BlockFace.SOUTH; - case 7: - return BlockFace.SOUTH_WEST; - default: - return BlockFace.WEST; - } - } - - /** - * Formats a price by inserting periods as thousand separators. - * - * @param price the price to format. - * @return the formatted price string. - */ - protected String betterPrice(long price) { - StringBuilder betterPrice = new StringBuilder(); - String[] splitPrice = String.valueOf(price).split(""); - int current = 0; - for (int a = splitPrice.length - 1; a > -1; a--) { - current++; - if (current > 3) { - betterPrice.append("."); - current = 1; - } - betterPrice.append(splitPrice[a]); - } - StringBuilder builder = new StringBuilder().append(betterPrice); - builder.reverse(); - return builder.toString(); - } - - /** - * Checks if an item stack has a specific enchantment. - * - * @param enchantment the enchantment to check. - * @param itemStack the item stack to check. - * @return true if the item stack has the enchantment, false otherwise. - */ - protected boolean hasEnchant(Enchantment enchantment, ItemStack itemStack) { - return itemStack.hasItemMeta() && itemStack.getItemMeta().hasEnchants() && itemStack.getItemMeta().hasEnchant(enchantment); - } - - /** - * Formats a cooldown timer for a player. - * - * @param player the player to format the timer for. - * @param cooldown the name of the cooldown. - * @return the formatted timer string. - */ - protected String timerFormat(Player player, String cooldown) { - return TimerBuilder.getStringTime(CooldownBuilder.getCooldownPlayer(cooldown, player) / 1000); - } - - /** - * Checks if a player is currently on a cooldown. - * - * @param player the player to check. - * @param cooldown the name of the cooldown. - * @return true if the player is on cooldown, false otherwise. - */ - protected boolean isCooldown(Player player, String cooldown) { - return isCooldown(player, cooldown, 0); - } - - /** - * Checks if a player is currently on a cooldown, and optionally sets a new cooldown timer. - * - * @param player the player to check. - * @param cooldown the name of the cooldown. - * @param timer the duration of the new cooldown timer, in seconds. - * @return true if the player is on cooldown, false otherwise. - */ - protected boolean isCooldown(Player player, String cooldown, int timer) { - if (CooldownBuilder.isCooldown(cooldown, player)) { - ActionBar.sendActionBar(player, String.format("§cVous devez attendre encore §6%s §cavant de pouvoir faire cette action.", timerFormat(player, cooldown))); - return true; - } - if (timer > 0) CooldownBuilder.addCooldown(cooldown, player, timer); - return false; - } - - /** - * Converts a stream of strings to a formatted list string with color codes. - * - * @param list the stream of strings to convert. - * @return the formatted list string. - */ - protected String toList(Stream list) { - return toList(list.collect(Collectors.toList()), "§e", "§6"); - } - - /** - * Converts a list of strings to a formatted list string with color codes. - * - * @param list the list of strings to convert. - * @return the formatted list string. - */ - protected String toList(List list) { - return toList(list, "§e", "§6§n"); - } - - /** - * Converts a list of strings to a formatted list string with specified color codes. - * - * @param list the list of strings to convert. - * @param color the primary color code. - * @param color2 the secondary color code. - * @return the formatted list string. - */ - protected String toList(List list, String color, String color2) { - if (list == null || list.size() == 0) return null; - if (list.size() == 1) return list.get(0); - StringBuilder str = new StringBuilder(); - for (int a = 0; a != list.size(); a++) { - if (a == list.size() - 1) str.append(color).append(" et ").append(color2); - else if (a != 0) str.append(color).append(", ").append(color2); - str.append(list.get(a)); - } - return str.toString(); - } - - - /** - * Formats a long value with a default grouping separator. - * - * @param value the long value to format. - * @return the formatted string. - */ - protected String format(long value) { - return format(value, ' '); - } - /** * Formats a long value with a specified grouping separator. * @@ -816,266 +80,66 @@ protected String format(long l, char c) { } /** - * Obtains a player's head item stack using the inventory configuration system. - * - * @param itemStack the original item stack. - * @param player the player whose head is to be represented. - * @return the modified item stack representing the player's head. - */ - public ItemStack playerHead(ItemStack itemStack, OfflinePlayer player) { - String name = itemStack.hasItemMeta() && itemStack.getItemMeta().hasDisplayName() ? itemStack.getItemMeta().getDisplayName() : null; - if (itemStack.getType().equals(Material.PLAYER_HEAD) && name != null && name.startsWith("HEAD")) { - SkullMeta meta = (SkullMeta) itemStack.getItemMeta(); - name = name.replace("HEAD", ""); - if (name.length() == 0) meta.setDisplayName(null); - else meta.setDisplayName(name); - meta.setOwningPlayer(player); - itemStack.setItemMeta(meta); - } - return itemStack; - } - - /** - * Creates an item stack representing a player's head. + * Reverses color codes in a message string. * - * @return the item stack representing a player's head. + * @param message the message string with color codes to reverse. + * @return the message string with reversed color codes. */ - protected ItemStack playerHead() { - return NmsVersion.nmsVersion.isNewMaterial() ? new ItemStack(Material.PLAYER_HEAD) : new ItemStack(getMaterial(397), 1, (byte) 3); - } + protected String colorReverse(String message) { + if (message == null) return null; - /** - * Obtains a service provider instance of a specified class from the plugin. - * - * @param plugin the plugin providing the service. - * @param classz the class of the service provider. - * @param the type of the service provider. - * @return the service provider instance, or null if not found. - */ - protected T getProvider(Plugin plugin, Class classz) { - RegisteredServiceProvider provider = plugin.getServer().getServicesManager().getRegistration(classz); - if (provider == null) return null; - return provider.getProvider() != null ? provider.getProvider() : null; - } + // Use StringBuilder for efficient string manipulation + StringBuilder result = new StringBuilder(message); + Matcher matcher = HEX_COLOR_PATTERN.matcher(result); - /** - * Gets the potion effect type corresponding to a given configuration string. - * - * @param configuration the configuration string representing the potion effect type. - * @return the potion effect type, or null if not found. - */ - protected PotionEffectType getPotion(String configuration) { - for (PotionEffectType effectType : PotionEffectType.values()) { - if (effectType.getName().equalsIgnoreCase(configuration)) { - return effectType; - } + // Process matches in reverse order to maintain correct indices + List matches = new ArrayList<>(); + while (matcher.find()) { + matches.add(new int[]{matcher.start(), matcher.end()}); } - return null; - } - - /** - * Executes a runnable asynchronously. - * - * @param plugin the plugin scheduling the task. - * @param runnable the runnable to execute. - */ - protected void runAsync(Plugin plugin, Runnable runnable) { - Bukkit.getScheduler().runTaskAsynchronously(plugin, runnable); - } - - - /** - * Converts a given time in seconds to a formatted string representation. - * - * @param second the time in seconds. - * @return the formatted time string. - */ - protected String getStringTime(long second) { - return TimerBuilder.getStringTime(second); - } - - /** - * Creates a player head item from a specified URL. - * - * @param url the URL of the texture to be applied to the head. - * @return the created {@link ItemStack} representing the player head. - */ - protected ItemStack createSkull(String url) { - ItemStack head = playerHead(); - if (url.isEmpty()) return head; - - SkullMeta headMeta = (SkullMeta) head.getItemMeta(); - GameProfile profile = new GameProfile(UUID.randomUUID(), "random_name"); - profile.getProperties().put("textures", new Property("textures", url)); - - try { - Field profileField = headMeta.getClass().getDeclaredField("profile"); - profileField.setAccessible(true); - profileField.set(headMeta, profile); - - } catch (IllegalArgumentException | NoSuchFieldException | SecurityException | IllegalAccessException error) { - error.printStackTrace(); + // Replace from end to beginning to avoid index shifting + for (int i = matches.size() - 1; i >= 0; i--) { + int start = matches.get(i)[0]; + int end = matches.get(i)[1]; + String color = result.substring(start, end); + String colorReplace = color.replace("§x", "#").replace("§", ""); + result.replace(start, end, colorReplace); } - head.setItemMeta(headMeta); - return head; - } - /** - * Checks if an item stack is a player head. - * - * @param itemStack the item stack to check. - * @return true if the item stack is a player head, false otherwise. - */ - protected boolean isPlayerHead(ItemStack itemStack) { - Material material = itemStack.getType(); - if (NmsVersion.nmsVersion.isNewMaterial()) return material.equals(Material.PLAYER_HEAD); - return (material.equals(getMaterial(397))) && (itemStack.getDurability() == 3); - } - - /** - * Gets the value of a private field from an object. - * - * @param object the object containing the field. - * @param field the name of the field to retrieve. - * @return the value of the field. - * @throws SecurityException if a security violation occurs. - * @throws NoSuchFieldException if the field does not exist. - * @throws IllegalArgumentException if an illegal argument is provided. - * @throws IllegalAccessException if the field is not accessible. - */ - protected Object getPrivateField(Object object, String field) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { - Class clazz = object.getClass(); - Field objectField = field.equals("commandMap") ? clazz.getDeclaredField(field) : field.equals("knownCommands") ? NmsVersion.nmsVersion.isNewMaterial() ? clazz.getSuperclass().getDeclaredField(field) : clazz.getDeclaredField(field) : null; - objectField.setAccessible(true); - Object result = objectField.get(object); - objectField.setAccessible(false); - return result; - } - - /** - * Unregisters a Bukkit command from the server. - * - * @param plugin the plugin that registered the command. - * @param command the {@link PluginCommand} to unregister. - */ - protected void unRegisterBukkitCommand(Plugin plugin, PluginCommand command) { - try { - Object result = getPrivateField(plugin.getServer().getPluginManager(), "commandMap"); - SimpleCommandMap commandMap = (SimpleCommandMap) result; - - Object map = getPrivateField(commandMap, "knownCommands"); - @SuppressWarnings("unchecked") HashMap knownCommands = (HashMap) map; - knownCommands.remove(command.getName()); - for (String alias : command.getAliases()) { - knownCommands.remove(alias); - } - knownCommands.remove(plugin.getName() + ":" + command.getName()); - for (String alias : command.getAliases()) { - knownCommands.remove(plugin.getName() + ":" + alias); + // Replace all remaining § with & + for (int i = 0; i < result.length(); i++) { + if (result.charAt(i) == '§') { + result.setCharAt(i, '&'); } - } catch (Exception e) { - e.printStackTrace(); } - } - - - /** - * Adds a glow effect to an item stack. - * - * @param itemStack the item stack to make glow. - */ - public void glow(ItemStack itemStack) { - ItemMeta itemMeta = itemStack.getItemMeta(); - itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS); - itemStack.setItemMeta(itemMeta); - } - /** - * Clears a player's inventory, removes potion effects, and resets their status. - * - * @param player the player to be cleared. - */ - protected void clearPlayer(Player player) { - player.getInventory().clear(); - player.getInventory().setBoots(null); - player.getInventory().setChestplate(null); - player.getInventory().setLeggings(null); - player.getInventory().setHelmet(null); - player.getPlayer().setItemOnCursor(null); - player.getPlayer().setFireTicks(0); - player.getPlayer().getOpenInventory().getTopInventory().clear(); - player.setGameMode(GameMode.SURVIVAL); - player.getPlayer().getActivePotionEffects().forEach(e -> { - player.getPlayer().removePotionEffect(e.getType()); - }); + return result.toString(); } /** - * Creates a progress bar string representation. - * - * @param current the current value. - * @param max the maximum value. - * @param totalBars the total number of bars. - * @param symbol the symbol used for the progress bar. - * @param completedColor the color for completed parts of the bar. - * @param notCompletedColor the color for incomplete parts of the bar. - * @return the string representation of the progress bar. - */ - public String getProgressBar(int current, int max, int totalBars, char symbol, String completedColor, String notCompletedColor) { - float percent = (float) current / max; - int progressBars = (int) (totalBars * percent); - - return Strings.repeat(completedColor + symbol, progressBars) + Strings.repeat(notCompletedColor + symbol, totalBars - progressBars); - } - - /** - * Creates a progress bar string representation using a ProgressBar object. + * Gives an item to the player. If the player's inventory is full, the item will be dropped on the ground. * - * @param current the current value. - * @param max the maximum value. - * @param progressBar the ProgressBar object containing bar settings. - * @return the string representation of the progress bar. + * @param player the player to receive the item. + * @param item the item to give to the player. */ - public String getProgressBar(int current, int max, ProgressBar progressBar) { - return this.getProgressBar(current, max, progressBar.getLength(), progressBar.getSymbol(), progressBar.getCompletedColor(), progressBar.getNotCompletedColor()); + protected void give(Player player, ItemStack item) { + if (hasInventoryFull(player)) { + player.getWorld().dropItem(player.getLocation(), item); + } else { + player.getInventory().addItem(item); + } } /** - * Checks if a player's inventory contains any items or armor. + * Checks if a permissible entity has a specific permission. * - * @param player the player whose inventory is to be checked. - * @return true if the inventory contains any items or armor, false otherwise. + * @param permissible the entity to check. + * @param permission the permission string to check for. + * @return true if the entity has the permission, false otherwise. */ - protected boolean inventoryHasItem(Player player) { - ItemStack itemStack = player.getInventory().getBoots(); - if (itemStack != null) { - return true; - } - - itemStack = player.getInventory().getChestplate(); - if (itemStack != null) { - return true; - } - - itemStack = player.getInventory().getLeggings(); - if (itemStack != null) { - return true; - } - - itemStack = player.getInventory().getHelmet(); - if (itemStack != null) { - return true; - } - - for (ItemStack currentItemStack : player.getInventory().getContents()) { - if (currentItemStack != null) { - return true; - } - } - - return false; + protected boolean hasPermission(Permissible permissible, String permission) { + return permissible.hasPermission(permission); } - -} +} \ No newline at end of file diff --git a/src/main/java/fr/maxlego08/items/zcore/utils/interfaces/CollectionConsumer.java b/src/main/java/fr/maxlego08/items/zcore/utils/interfaces/CollectionConsumer.java deleted file mode 100644 index 8df8631..0000000 --- a/src/main/java/fr/maxlego08/items/zcore/utils/interfaces/CollectionConsumer.java +++ /dev/null @@ -1,10 +0,0 @@ -package fr.maxlego08.items.zcore.utils.interfaces; - -import java.util.Collection; - -@FunctionalInterface -public interface CollectionConsumer { - - Collection accept(T t); - -} diff --git a/src/main/java/fr/maxlego08/items/zcore/utils/interfaces/StringConsumer.java b/src/main/java/fr/maxlego08/items/zcore/utils/interfaces/StringConsumer.java deleted file mode 100644 index 897c4fc..0000000 --- a/src/main/java/fr/maxlego08/items/zcore/utils/interfaces/StringConsumer.java +++ /dev/null @@ -1,8 +0,0 @@ -package fr.maxlego08.items.zcore.utils.interfaces; - -@FunctionalInterface -public interface StringConsumer { - - String accept(T t); - -} diff --git a/src/main/java/fr/maxlego08/items/zcore/utils/inventory/Pagination.java b/src/main/java/fr/maxlego08/items/zcore/utils/inventory/Pagination.java deleted file mode 100644 index aecd099..0000000 --- a/src/main/java/fr/maxlego08/items/zcore/utils/inventory/Pagination.java +++ /dev/null @@ -1,39 +0,0 @@ -package fr.maxlego08.items.zcore.utils.inventory; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -public class Pagination { - - public List paginateReverse(List list, int inventorySize, int page) { - List currentList = new ArrayList<>(); - if (page == 0) page = 1; - int idStart = list.size() - 1 - ((page - 1) * inventorySize); - int idEnd = idStart - inventorySize; - if (idEnd < list.size() - inventorySize && list.size() < inventorySize * page) idEnd = -1; - for (int a = idStart; a != idEnd; a--) - currentList.add(list.get(a)); - return currentList; - } - - public List paginate(List list, int size, int page) { - List currentList = new ArrayList<>(); - if (page <= 0) page = 1; - int idStart = ((page - 1)) * size; - int idEnd = idStart + size; - if (idEnd > list.size()) idEnd = list.size(); - for (int a = idStart; a != idEnd; a++) - currentList.add(list.get(a)); - return currentList; - } - - public List paginateReverse(Map map, int size, int page) { - return paginateReverse(new ArrayList<>(map.values()), size, page); - } - - public List paginate(Map map, int inventorySize, int page) { - return paginate(new ArrayList<>(map.values()), inventorySize, page); - } - -} diff --git a/src/main/java/fr/maxlego08/items/zcore/utils/loader/Loader.java b/src/main/java/fr/maxlego08/items/zcore/utils/loader/Loader.java deleted file mode 100644 index d94fa35..0000000 --- a/src/main/java/fr/maxlego08/items/zcore/utils/loader/Loader.java +++ /dev/null @@ -1,25 +0,0 @@ -package fr.maxlego08.items.zcore.utils.loader; - -import org.bukkit.configuration.file.YamlConfiguration; - -public interface Loader { - - /** - * Load object from yml - * - * @param configuration - * @param path - * @return element - */ - T load(YamlConfiguration configuration, String path); - - /** - * Save object to yml - * - * @param object - * @param configuration - * @param path - */ - void save(T object, YamlConfiguration configuration, String path); - -} diff --git a/src/main/java/fr/maxlego08/items/zcore/utils/map/OptionalHashMap.java b/src/main/java/fr/maxlego08/items/zcore/utils/map/OptionalHashMap.java deleted file mode 100644 index 14d3050..0000000 --- a/src/main/java/fr/maxlego08/items/zcore/utils/map/OptionalHashMap.java +++ /dev/null @@ -1,32 +0,0 @@ -package fr.maxlego08.items.zcore.utils.map; - -import java.util.HashMap; -import java.util.Optional; - -public class OptionalHashMap extends HashMap implements OptionalMap{ - - /** - * - */ - private static final long serialVersionUID = -1389669310403530512L; - - /** - * - * @param key - * @return {@link Optional} - */ - public Optional getOptional(K key) { - V value = super.getOrDefault(key, null); - return value == null ? Optional.empty() : Optional.of(value); - } - - /** - * - * @param key - * @return true if is present - */ - public boolean isPresent(K key) { - return getOptional(key).isPresent(); - } - -} diff --git a/src/main/java/fr/maxlego08/items/zcore/utils/map/OptionalMap.java b/src/main/java/fr/maxlego08/items/zcore/utils/map/OptionalMap.java deleted file mode 100644 index 757b5e0..0000000 --- a/src/main/java/fr/maxlego08/items/zcore/utils/map/OptionalMap.java +++ /dev/null @@ -1,22 +0,0 @@ -package fr.maxlego08.items.zcore.utils.map; - -import java.util.Map; -import java.util.Optional; - -public interface OptionalMap extends Map { - - /** - * - * @param key - * @return - */ - Optional getOptional(K key); - - /** - * - * @param key - * @return - */ - boolean isPresent(K key); - -} diff --git a/src/main/java/fr/maxlego08/items/zcore/utils/nms/Base64ItemStack.java b/src/main/java/fr/maxlego08/items/zcore/utils/nms/Base64ItemStack.java index e9a5e3c..83c7cc3 100644 --- a/src/main/java/fr/maxlego08/items/zcore/utils/nms/Base64ItemStack.java +++ b/src/main/java/fr/maxlego08/items/zcore/utils/nms/Base64ItemStack.java @@ -6,6 +6,8 @@ import org.bukkit.util.io.BukkitObjectOutputStream; import java.io.*; +import java.util.logging.Level; +import java.util.logging.Logger; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; @@ -15,6 +17,7 @@ */ public class Base64ItemStack { + private static final Logger LOGGER = Logger.getLogger(Base64ItemStack.class.getName()); public static String encode(ItemStack item) { try { @@ -25,7 +28,7 @@ public static String encode(ItemStack item) { objectOutputStream.close(); return Base64.encode(byteArrayOutputStream.toByteArray()); } catch (IOException exception) { - exception.printStackTrace(); + LOGGER.log(Level.SEVERE, "Failed to encode ItemStack to Base64", exception); return null; } } @@ -39,7 +42,7 @@ public static ItemStack decode(String data) { objectInputStream.close(); return item; } catch (IOException | ClassNotFoundException exception) { - exception.printStackTrace(); + LOGGER.log(Level.SEVERE, "Failed to decode Base64 to ItemStack", exception); return null; } } diff --git a/src/main/java/fr/maxlego08/items/zcore/utils/nms/ItemStackCompound.java b/src/main/java/fr/maxlego08/items/zcore/utils/nms/ItemStackCompound.java index 079ecd9..1a39894 100644 --- a/src/main/java/fr/maxlego08/items/zcore/utils/nms/ItemStackCompound.java +++ b/src/main/java/fr/maxlego08/items/zcore/utils/nms/ItemStackCompound.java @@ -1,7 +1,10 @@ package fr.maxlego08.items.zcore.utils.nms; +import org.bukkit.Bukkit; import org.bukkit.inventory.ItemStack; +import java.util.logging.Level; + public class ItemStackCompound { private final EnumReflectionCompound reflection; @@ -62,7 +65,7 @@ public ItemStack setString(ItemStack itemStack, String key, String value) { return this.applyCompound(itemStack, compoundObject); } catch (Exception e) { - e.printStackTrace(); + Bukkit.getLogger().log(Level.SEVERE, "Failed to access NBT tag compound", e); } return null; @@ -78,7 +81,7 @@ public String getString(ItemStack itemStack, String key) { .invoke(compoundObject, new Object[]{key}); } catch (Exception e) { - e.printStackTrace(); + Bukkit.getLogger().log(Level.SEVERE, "Failed to access NBT tag compound", e); } return null; @@ -94,7 +97,7 @@ public double getDouble(ItemStack itemStack, String key) { .invoke(compoundObject, new Object[]{key}); } catch (Exception e) { - e.printStackTrace(); + Bukkit.getLogger().log(Level.SEVERE, "Failed to access NBT tag compound", e); } return 0; @@ -110,7 +113,7 @@ public long getLong(ItemStack itemStack, String key) { .invoke(compoundObject, new Object[]{key}); } catch (Exception e) { - e.printStackTrace(); + Bukkit.getLogger().log(Level.SEVERE, "Failed to access NBT tag compound", e); } return 0; @@ -126,7 +129,7 @@ public int getInt(ItemStack itemStack, String key) { .invoke(compoundObject, new Object[]{key}); } catch (Exception e) { - e.printStackTrace(); + Bukkit.getLogger().log(Level.SEVERE, "Failed to access NBT tag compound", e); } return 0; @@ -142,7 +145,7 @@ public float getFloat(ItemStack itemStack, String key) { .invoke(compoundObject, new Object[]{key}); } catch (Exception e) { - e.printStackTrace(); + Bukkit.getLogger().log(Level.SEVERE, "Failed to access NBT tag compound", e); } return 0; @@ -158,7 +161,7 @@ public boolean getBoolean(ItemStack itemStack, String key) { .invoke(compoundObject, new Object[]{key}); } catch (Exception e) { - e.printStackTrace(); + Bukkit.getLogger().log(Level.SEVERE, "Failed to access NBT tag compound", e); } return false; @@ -175,7 +178,7 @@ public ItemStack setInt(ItemStack itemStack, String key, int value) { return this.applyCompound(itemStack, compoundObject); } catch (Exception e) { - e.printStackTrace(); + Bukkit.getLogger().log(Level.SEVERE, "Failed to access NBT tag compound", e); } return null; @@ -192,7 +195,7 @@ public ItemStack setLong(ItemStack itemStack, String key, long value) { return this.applyCompound(itemStack, compoundObject); } catch (Exception e) { - e.printStackTrace(); + Bukkit.getLogger().log(Level.SEVERE, "Failed to access NBT tag compound", e); } return null; @@ -209,7 +212,7 @@ public ItemStack setFloat(ItemStack itemStack, String key, float value) { return this.applyCompound(itemStack, compoundObject); } catch (Exception e) { - e.printStackTrace(); + Bukkit.getLogger().log(Level.SEVERE, "Failed to access NBT tag compound", e); } return null; @@ -226,7 +229,7 @@ public ItemStack setBoolean(ItemStack itemStack, String key, boolean value) { return this.applyCompound(itemStack, compoundObject); } catch (Exception e) { - e.printStackTrace(); + Bukkit.getLogger().log(Level.SEVERE, "Failed to access NBT tag compound", e); } return null; @@ -243,7 +246,7 @@ public ItemStack setDouble(ItemStack itemStack, String key, double value) { return this.applyCompound(itemStack, compoundObject); } catch (Exception e) { - e.printStackTrace(); + Bukkit.getLogger().log(Level.SEVERE, "Failed to access NBT tag compound", e); } return null; @@ -269,7 +272,7 @@ public boolean isKey(ItemStack itemStack, String key) { .getMethod(this.reflection.getMethodHaskey(), new Class[]{String.class}) .invoke(nbttagCompound, new Object[]{key}); } catch (Exception e) { - e.printStackTrace(); + Bukkit.getLogger().log(Level.SEVERE, "Failed to access NBT tag compound", e); } return false; diff --git a/src/main/java/fr/maxlego08/items/zcore/utils/nms/ItemStackUtils.java b/src/main/java/fr/maxlego08/items/zcore/utils/nms/ItemStackUtils.java index 7ce47a9..0fd308c 100644 --- a/src/main/java/fr/maxlego08/items/zcore/utils/nms/ItemStackUtils.java +++ b/src/main/java/fr/maxlego08/items/zcore/utils/nms/ItemStackUtils.java @@ -6,11 +6,14 @@ import java.io.*; import java.lang.reflect.Constructor; +import java.util.logging.Level; +import java.util.logging.Logger; import java.util.zip.GZIPInputStream; public class ItemStackUtils { private static final NmsVersion NMS_VERSION = NmsVersion.nmsVersion; + private static final Logger LOGGER = Logger.getLogger(ItemStackUtils.class.getName()); /** * Change {@link ItemStack} to {@link String} @@ -153,7 +156,7 @@ public Class getClassz() { try { localClass = Class.forName(var3); } catch (ClassNotFoundException localClassNotFoundException) { - localClassNotFoundException.printStackTrace(); + LOGGER.log(Level.SEVERE, "Failed to find NMS class: " + var3, localClassNotFoundException); } return localClass; } diff --git a/src/main/java/fr/maxlego08/items/zcore/utils/players/ActionBar.java b/src/main/java/fr/maxlego08/items/zcore/utils/players/ActionBar.java deleted file mode 100644 index 0a95016..0000000 --- a/src/main/java/fr/maxlego08/items/zcore/utils/players/ActionBar.java +++ /dev/null @@ -1,66 +0,0 @@ -package fr.maxlego08.items.zcore.utils.players; - -import fr.maxlego08.items.zcore.utils.nms.NmsVersion; -import net.md_5.bungee.api.ChatMessageType; -import net.md_5.bungee.api.chat.TextComponent; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; - -public class ActionBar { - - private static Class craftPlayerClass; - private static Class packetClass; - private static Method getHandleMethod; - private static Field playerConnectionField; - private static Constructor constructorPacket; - private static Constructor constructorComponent; - - static { - String nmsVersionAsString = Bukkit.getServer().getClass().getPackage().getName(); - nmsVersionAsString = nmsVersionAsString.substring(nmsVersionAsString.lastIndexOf(".") + 1); - - try { - craftPlayerClass = Class.forName("org.bukkit.craftbukkit." + nmsVersionAsString + ".entity.CraftPlayer"); - Class packetPlayOutChatClass = Class.forName("net.minecraft.server." + nmsVersionAsString + ".PacketPlayOutChat"); - packetClass = Class.forName("net.minecraft.server." + nmsVersionAsString + ".Packet"); - Class iChatBaseComponentClass = Class.forName("net.minecraft.server." + nmsVersionAsString + ".IChatBaseComponent"); - - getHandleMethod = craftPlayerClass.getMethod("getHandle"); - playerConnectionField = getHandleMethod.getReturnType().getField("playerConnection"); - - Class chatComponentTextClass = Class.forName("net.minecraft.server." + nmsVersionAsString + ".ChatComponentText"); - - constructorComponent = chatComponentTextClass.getConstructor(String.class); - constructorPacket = packetPlayOutChatClass.getConstructor(iChatBaseComponentClass, Byte.TYPE); - } catch (Exception ignored) { - } - } - - public static void sendActionBar(Player player, String message) { - - if (!player.isOnline()) { - return; - } - - if (NmsVersion.nmsVersion.getVersion() >= NmsVersion.V_1_10.getVersion()) { - player.spigot().sendMessage(ChatMessageType.ACTION_BAR, new TextComponent(TextComponent.fromLegacyText(message))); - return; - } - - try { - Object craftPlayer = craftPlayerClass.cast(player); - Object packet = constructorComponent.newInstance(message); - Object packetContent = constructorPacket.newInstance(packet, (byte) 2); - Object serverPlayer = getHandleMethod.invoke(craftPlayer); - packet = playerConnectionField.get(serverPlayer); - Method packetMethod = packet.getClass().getDeclaredMethod("sendPacket", packetClass); - packetMethod.invoke(packet, packetContent); - } catch (Exception error) { - error.printStackTrace(); - } - } -} \ No newline at end of file diff --git a/src/main/java/fr/maxlego08/items/zcore/utils/players/BarApi.java b/src/main/java/fr/maxlego08/items/zcore/utils/players/BarApi.java deleted file mode 100644 index a9d1ad7..0000000 --- a/src/main/java/fr/maxlego08/items/zcore/utils/players/BarApi.java +++ /dev/null @@ -1,168 +0,0 @@ -package fr.maxlego08.items.zcore.utils.players; - -import fr.maxlego08.items.zcore.utils.interfaces.StringConsumer; -import org.bukkit.Bukkit; -import org.bukkit.boss.BarColor; -import org.bukkit.boss.BarFlag; -import org.bukkit.boss.BarStyle; -import org.bukkit.boss.BossBar; -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; - -import java.util.Timer; -import java.util.TimerTask; - -public class BarApi { - - private final Plugin plugin; - private String message; - private BarColor color = BarColor.BLUE; - private BarStyle style = BarStyle.SOLID; - private BarFlag[] flags = new BarFlag[] {}; - private long delay = 5; - private boolean addAll = true; - private StringConsumer consumer; - private boolean personnal = false; - private Player player; - - public BarApi(Plugin plugin, String message, BarColor color, BarStyle style, BarFlag... flags) { - this(plugin); - this.message = message; - this.color = color; - this.style = style; - this.flags = flags; - } - - public BarApi(Plugin plugin) { - this.plugin = plugin; - } - - public BarApi(Plugin plugin, String message) { - this(plugin); - this.message = message; - } - - public BarApi delay(long delay) { - this.delay = delay; - return this; - } - - public BarApi color(BarColor color) { - this.color = color; - return this; - } - - public BarApi style(BarStyle style) { - this.style = style; - return this; - } - - public BarApi flags(BarFlag... flags) { - this.flags = flags; - return this; - } - - public BarApi consumer(StringConsumer consumer) { - this.consumer = consumer; - return this; - } - - public BarApi all() { - addAll = true; - return this; - } - - public BarApi personnal() { - personnal = true; - return this; - } - - /** - * @return the message - */ - public String getMessage() { - return message; - } - - /** - * @return the color - */ - public BarColor getColor() { - return color; - } - - /** - * @return the style - */ - public BarStyle getStyle() { - return style; - } - - /** - * @return the flags - */ - public BarFlag[] getFlags() { - return flags; - } - - public void start() { - - if (player != null) - startPersonnal(player); - else if (personnal) - startPersonnal(); - else { - BossBar bar = Bukkit.createBossBar(message, color, style, flags); - if (addAll) - Bukkit.getOnlinePlayers().forEach(tmpPlayer -> bar.addPlayer(tmpPlayer)); - barTask(bar, null); - } - } - - private void startPersonnal() { - Bukkit.getOnlinePlayers().forEach(tmpPlayer -> startPersonnal(tmpPlayer)); - } - - private void startPersonnal(Player player) { - - BossBar bar = Bukkit.createBossBar(consumer != null ? consumer.accept(player) : message, color, style, flags); - bar.addPlayer(player); - barTask(bar, () -> bar.setTitle(consumer != null ? consumer.accept(player) : message)); - - } - - private void barTask(BossBar bar, Runnable runnable) { - new Timer().scheduleAtFixedRate(new TimerTask() { - - private double barC = 1.0; - - @Override - public void run() { - - if (!plugin.isEnabled()) { - cancel(); - return; - } - - if (barC <= 0.0) { - cancel(); - bar.removeAll(); - return; - } - - if (runnable != null) - runnable.run(); - - bar.setProgress(barC); - barC -= 0.001; - - } - }, 0, delay); - } - - public BarApi user(Player player) { - this.player = player; - return this; - } - -} diff --git a/src/main/java/fr/maxlego08/items/zcore/utils/plugins/Metrics.java b/src/main/java/fr/maxlego08/items/zcore/utils/plugins/Metrics.java deleted file mode 100644 index b0515c0..0000000 --- a/src/main/java/fr/maxlego08/items/zcore/utils/plugins/Metrics.java +++ /dev/null @@ -1,721 +0,0 @@ -package fr.maxlego08.items.zcore.utils.plugins; - -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.JsonPrimitive; -import org.bukkit.Bukkit; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.RegisteredServiceProvider; -import org.bukkit.plugin.ServicePriority; - -import javax.net.ssl.HttpsURLConnection; -import java.io.*; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.concurrent.Callable; -import java.util.logging.Level; -import java.util.zip.GZIPOutputStream; - -/** - * bStats collects some data for plugin authors. - *

- * Check out https://bStats.org/ to learn more about bStats! - */ -public class Metrics { - - static { - // You can use the property to disable the check in your test environment - if (System.getProperty("bstats.relocatecheck") == null || !System.getProperty("bstats.relocatecheck").equals("false")) { - // Maven's Relocate is clever and changes strings, too. So we have to use this little "trick" ... :D - final String defaultPackage = new String( - new byte[]{'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's', '.', 'b', 'u', 'k', 'k', 'i', 't'}); - final String examplePackage = new String(new byte[]{'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); - // We want to make sure nobody just copy & pastes the example and use the wrong package names - if (Metrics.class.getPackage().getName().equals(defaultPackage) || Metrics.class.getPackage().getName().equals(examplePackage)) { - throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); - } - } - } - - // The version of this bStats class - public static final int B_STATS_VERSION = 1; - - // The url to which the data is sent - private static final String URL = "https://bStats.org/submitData/bukkit"; - - // Is bStats enabled on this server? - private boolean enabled; - - // Should failed requests be logged? - private static boolean logFailedRequests; - - // Should the sent data be logged? - private static boolean logSentData; - - // Should the response text be logged? - private static boolean logResponseStatusText; - - // The uuid of the server - private static String serverUUID; - - // The plugin - private final Plugin plugin; - - // The plugin id - private final int pluginId; - - // A list with all custom charts - private final List charts = new ArrayList<>(); - - /** - * Class constructor. - * - * @param plugin The plugin which stats should be submitted. - * @param pluginId The id of the plugin. - * It can be found at What is my plugin id? - */ - @SuppressWarnings("deprecation") - public Metrics(Plugin plugin, int pluginId) { - if (plugin == null) { - throw new IllegalArgumentException("Plugin cannot be null!"); - } - this.plugin = plugin; - this.pluginId = pluginId; - - // Get the config files - File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); - File configFile = new File(bStatsFolder, "config.yml"); - YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); - - // Check if the config files exists - if (!config.isSet("serverUuid")) { - - // Add default values - config.addDefault("enabled", true); - // Every server gets it's unique random id. - config.addDefault("serverUuid", UUID.randomUUID().toString()); - // Should failed request be logged? - config.addDefault("logFailedRequests", false); - // Should the sent data be logged? - config.addDefault("logSentData", false); - // Should the response text be logged? - config.addDefault("logResponseStatusText", false); - - // Inform the server owners about bStats - config.options().header( - "bStats collects some data for plugin authors like how many servers are using their plugins.\n" + - "To honor their work, you should not disable it.\n" + - "This has nearly no effect on the server performance!\n" + - "Check out https://bStats.org/ to learn more :)" - ).copyDefaults(true); - try { - config.save(configFile); - } catch (IOException ignored) { } - } - - // Load the data - enabled = config.getBoolean("enabled", true); - serverUUID = config.getString("serverUuid"); - logFailedRequests = config.getBoolean("logFailedRequests", false); - logSentData = config.getBoolean("logSentData", false); - logResponseStatusText = config.getBoolean("logResponseStatusText", false); - - if (enabled) { - boolean found = false; - // Search for all other bStats Metrics classes to see if we are the first one - for (Class service : Bukkit.getServicesManager().getKnownServices()) { - try { - service.getField("B_STATS_VERSION"); // Our identifier :) - found = true; // We aren't the first - break; - } catch (NoSuchFieldException ignored) { } - } - // Register our service - Bukkit.getServicesManager().register(Metrics.class, this, plugin, ServicePriority.Normal); - if (!found) { - // We are the first! - startSubmitting(); - } - } - } - - /** - * Checks if bStats is enabled. - * - * @return Whether bStats is enabled or not. - */ - public boolean isEnabled() { - return enabled; - } - - /** - * Adds a custom chart. - * - * @param chart The chart to add. - */ - public void addCustomChart(CustomChart chart) { - if (chart == null) { - throw new IllegalArgumentException("Chart cannot be null!"); - } - charts.add(chart); - } - - /** - * Starts the Scheduler which submits our data every 30 minutes. - */ - private void startSubmitting() { - final Timer timer = new Timer(true); // We use a timer cause the Bukkit scheduler is affected by server lags - timer.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - if (!plugin.isEnabled()) { // Plugin was disabled - timer.cancel(); - return; - } - // Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler - // Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;) - Bukkit.getScheduler().runTask(plugin, () -> submitData()); - } - }, 1000 * 60 * 5, 1000 * 60 * 30); - // Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start - // WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted! - // WARNING: Just don't do it! - } - - /** - * Gets the plugin specific data. - * This method is called using Reflection. - * - * @return The plugin specific data. - */ - public JsonObject getPluginData() { - JsonObject data = new JsonObject(); - - String pluginName = "zAuctionHouseV2"; - String pluginVersion = plugin.getDescription().getVersion(); - - data.addProperty("pluginName", pluginName); // Append the name of the plugin - data.addProperty("id", pluginId); // Append the id of the plugin - data.addProperty("pluginVersion", pluginVersion); // Append the version of the plugin - JsonArray customCharts = new JsonArray(); - for (CustomChart customChart : charts) { - // Add the data of the custom charts - JsonObject chart = customChart.getRequestJsonObject(); - if (chart == null) { // If the chart is null, we skip it - continue; - } - customCharts.add(chart); - } - data.add("customCharts", customCharts); - - return data; - } - - /** - * Gets the server specific data. - * - * @return The server specific data. - */ - private JsonObject getServerData() { - // Minecraft specific data - int playerAmount; - try { - // Around MC 1.8 the return type was changed to a collection from an array, - // This fixes java.lang.NoSuchMethodError: org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection; - Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers"); - playerAmount = onlinePlayersMethod.getReturnType().equals(Collection.class) - ? ((Collection) onlinePlayersMethod.invoke(Bukkit.getServer())).size() - : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length; - } catch (Exception e) { - playerAmount = Bukkit.getOnlinePlayers().size(); // Just use the new method if the Reflection failed - } - int onlineMode = Bukkit.getOnlineMode() ? 1 : 0; - String bukkitVersion = Bukkit.getVersion(); - String bukkitName = Bukkit.getName(); - - // OS/Java specific data - String javaVersion = System.getProperty("java.version"); - String osName = System.getProperty("os.name"); - String osArch = System.getProperty("os.arch"); - String osVersion = System.getProperty("os.version"); - int coreCount = Runtime.getRuntime().availableProcessors(); - - JsonObject data = new JsonObject(); - - data.addProperty("serverUUID", serverUUID); - - data.addProperty("playerAmount", playerAmount); - data.addProperty("onlineMode", onlineMode); - data.addProperty("bukkitVersion", bukkitVersion); - data.addProperty("bukkitName", bukkitName); - - data.addProperty("javaVersion", javaVersion); - data.addProperty("osName", osName); - data.addProperty("osArch", osArch); - data.addProperty("osVersion", osVersion); - data.addProperty("coreCount", coreCount); - - return data; - } - - /** - * Collects the data and sends it afterwards. - */ - @SuppressWarnings("deprecation") - private void submitData() { - final JsonObject data = getServerData(); - - JsonArray pluginData = new JsonArray(); - // Search for all other bStats Metrics classes to get their plugin data - for (Class service : Bukkit.getServicesManager().getKnownServices()) { - try { - service.getField("B_STATS_VERSION"); // Our identifier :) - - for (RegisteredServiceProvider provider : Bukkit.getServicesManager().getRegistrations(service)) { - try { - Object plugin = provider.getService().getMethod("getPluginData").invoke(provider.getProvider()); - if (plugin instanceof JsonObject) { - pluginData.add((JsonObject) plugin); - } else { // old bstats version compatibility - try { - Class jsonObjectJsonSimple = Class.forName("org.json.simple.JSONObject"); - if (plugin.getClass().isAssignableFrom(jsonObjectJsonSimple)) { - Method jsonStringGetter = jsonObjectJsonSimple.getDeclaredMethod("toJSONString"); - jsonStringGetter.setAccessible(true); - String jsonString = (String) jsonStringGetter.invoke(plugin); - JsonObject object = new JsonParser().parse(jsonString).getAsJsonObject(); - pluginData.add(object); - } - } catch (ClassNotFoundException e) { - // minecraft version 1.14+ - if (logFailedRequests) { - this.plugin.getLogger().log(Level.SEVERE, "Encountered unexpected exception", e); - } - } - } - } catch (NullPointerException | NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) { } - } - } catch (NoSuchFieldException ignored) { } - } - - data.add("plugins", pluginData); - - // Create a new thread for the connection to the bStats server - new Thread(() -> { - try { - // Send the data - sendData(plugin, data); - } catch (Exception e) { - // Something went wrong! :( - if (logFailedRequests) { - plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e); - } - } - }).start(); - } - - /** - * Sends the data to the bStats server. - * - * @param plugin Any plugin. It's just used to get a logger instance. - * @param data The data to send. - * @throws Exception If the request failed. - */ - private static void sendData(Plugin plugin, JsonObject data) throws Exception { - if (data == null) { - throw new IllegalArgumentException("Data cannot be null!"); - } - if (Bukkit.isPrimaryThread()) { - throw new IllegalAccessException("This method must not be called from the main thread!"); - } - if (logSentData) { - plugin.getLogger().info("Sending data to bStats: " + data); - } - HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection(); - - // Compress the data to save bandwidth - byte[] compressedData = compress(data.toString()); - - // Add headers - connection.setRequestMethod("POST"); - connection.addRequestProperty("Accept", "application/json"); - connection.addRequestProperty("Connection", "close"); - connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request - connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); - connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format - connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION); - - // Send data - connection.setDoOutput(true); - try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { - outputStream.write(compressedData); - } - - StringBuilder builder = new StringBuilder(); - try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { - String line; - while ((line = bufferedReader.readLine()) != null) { - builder.append(line); - } - } - - if (logResponseStatusText) { - plugin.getLogger().info("Sent data to bStats and received response: " + builder); - } - } - - /** - * Gzips the given String. - * - * @param str The string to gzip. - * @return The gzipped String. - * @throws IOException If the compression failed. - */ - private static byte[] compress(final String str) throws IOException { - if (str == null) { - return null; - } - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) { - gzip.write(str.getBytes(StandardCharsets.UTF_8)); - } - return outputStream.toByteArray(); - } - - /** - * Represents a custom chart. - */ - public static abstract class CustomChart { - - // The id of the chart - final String chartId; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - */ - CustomChart(String chartId) { - if (chartId == null || chartId.isEmpty()) { - throw new IllegalArgumentException("ChartId cannot be null or empty!"); - } - this.chartId = chartId; - } - - private JsonObject getRequestJsonObject() { - JsonObject chart = new JsonObject(); - chart.addProperty("chartId", chartId); - try { - JsonObject data = getChartData(); - if (data == null) { - // If the data is null we don't send the chart. - return null; - } - chart.add("data", data); - } catch (Throwable t) { - if (logFailedRequests) { - Bukkit.getLogger().log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, t); - } - return null; - } - return chart; - } - - protected abstract JsonObject getChartData() throws Exception; - - } - - /** - * Represents a custom simple pie. - */ - public static class SimplePie extends CustomChart { - - private final Callable callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public SimplePie(String chartId, Callable callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObject getChartData() throws Exception { - JsonObject data = new JsonObject(); - String value = callable.call(); - if (value == null || value.isEmpty()) { - // Null = skip the chart - return null; - } - data.addProperty("value", value); - return data; - } - } - - /** - * Represents a custom advanced pie. - */ - public static class AdvancedPie extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public AdvancedPie(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObject getChartData() throws Exception { - JsonObject data = new JsonObject(); - JsonObject values = new JsonObject(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue() == 0) { - continue; // Skip this invalid - } - allSkipped = false; - values.addProperty(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - data.add("values", values); - return data; - } - } - - /** - * Represents a custom drilldown pie. - */ - public static class DrilldownPie extends CustomChart { - - private final Callable>> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public DrilldownPie(String chartId, Callable>> callable) { - super(chartId); - this.callable = callable; - } - - @Override - public JsonObject getChartData() throws Exception { - JsonObject data = new JsonObject(); - JsonObject values = new JsonObject(); - Map> map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean reallyAllSkipped = true; - for (Map.Entry> entryValues : map.entrySet()) { - JsonObject value = new JsonObject(); - boolean allSkipped = true; - for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { - value.addProperty(valueEntry.getKey(), valueEntry.getValue()); - allSkipped = false; - } - if (!allSkipped) { - reallyAllSkipped = false; - values.add(entryValues.getKey(), value); - } - } - if (reallyAllSkipped) { - // Null = skip the chart - return null; - } - data.add("values", values); - return data; - } - } - - /** - * Represents a custom single line chart. - */ - public static class SingleLineChart extends CustomChart { - - private final Callable callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public SingleLineChart(String chartId, Callable callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObject getChartData() throws Exception { - JsonObject data = new JsonObject(); - int value = callable.call(); - if (value == 0) { - // Null = skip the chart - return null; - } - data.addProperty("value", value); - return data; - } - - } - - /** - * Represents a custom multi line chart. - */ - public static class MultiLineChart extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public MultiLineChart(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObject getChartData() throws Exception { - JsonObject data = new JsonObject(); - JsonObject values = new JsonObject(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue() == 0) { - continue; // Skip this invalid - } - allSkipped = false; - values.addProperty(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - data.add("values", values); - return data; - } - - } - - /** - * Represents a custom simple bar chart. - */ - public static class SimpleBarChart extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public SimpleBarChart(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObject getChartData() throws Exception { - JsonObject data = new JsonObject(); - JsonObject values = new JsonObject(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - for (Map.Entry entry : map.entrySet()) { - JsonArray categoryValues = new JsonArray(); - categoryValues.add(new JsonPrimitive(entry.getValue())); - values.add(entry.getKey(), categoryValues); - } - data.add("values", values); - return data; - } - - } - - /** - * Represents a custom advanced bar chart. - */ - public static class AdvancedBarChart extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public AdvancedBarChart(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObject getChartData() throws Exception { - JsonObject data = new JsonObject(); - JsonObject values = new JsonObject(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue().length == 0) { - continue; // Skip this invalid - } - allSkipped = false; - JsonArray categoryValues = new JsonArray(); - for (int categoryValue : entry.getValue()) { - categoryValues.add(new JsonPrimitive(categoryValue)); - } - values.add(entry.getKey(), categoryValues); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - data.add("values", values); - return data; - } - } - -} \ No newline at end of file diff --git a/src/main/java/fr/maxlego08/items/zcore/utils/plugins/VersionChecker.java b/src/main/java/fr/maxlego08/items/zcore/utils/plugins/VersionChecker.java deleted file mode 100644 index 69c3aca..0000000 --- a/src/main/java/fr/maxlego08/items/zcore/utils/plugins/VersionChecker.java +++ /dev/null @@ -1,112 +0,0 @@ -package fr.maxlego08.items.zcore.utils.plugins; - -import fr.maxlego08.items.zcore.enums.Message; -import fr.maxlego08.items.zcore.logger.Logger; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.plugin.Plugin; -import org.bukkit.scheduler.BukkitRunnable; - -import java.io.IOException; -import java.net.URL; -import java.net.URLConnection; -import java.util.Scanner; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; - -/** - * - * @author Maxlego08 - * - */ -public class VersionChecker implements Listener { - - private final String URL_API = "https://groupez.dev/api/v1/resource/version/%s"; - private final String URL_RESOURCE = "https://groupez.dev/resources/%s"; - private final Plugin plugin; - private final int pluginID; - private boolean useLastVersion = false; - - /** - * Class constructor - * - * @param plugin - * @param pluginID - */ - public VersionChecker(Plugin plugin, int pluginID) { - super(); - this.plugin = plugin; - this.pluginID = pluginID; - } - - /** - * Allows to check if the plugin version is up to date. - */ - public void useLastVersion() { - - Bukkit.getPluginManager().registerEvents(this, this.plugin); // Register - // event - - String pluginVersion = plugin.getDescription().getVersion(); - AtomicBoolean atomicBoolean = new AtomicBoolean(); - this.getVersion(version -> { - - long ver = Long.valueOf(version.replace(".", "")); - long plVersion = Long.valueOf(pluginVersion.replace(".", "")); - atomicBoolean.set(plVersion >= ver); - this.useLastVersion = atomicBoolean.get(); - if (atomicBoolean.get()) - Logger.info("No update available."); - else { - Logger.info("New update available. Your version: " + pluginVersion + ", latest version: " + version); - Logger.info("Download plugin here: " + String.format(URL_RESOURCE, this.pluginID)); - } - }); - - } - - @EventHandler - public void onConnect(PlayerJoinEvent event) { - final Player player = event.getPlayer(); - if (!useLastVersion && event.getPlayer().hasPermission("zplugin.notifs")) { - new BukkitRunnable() { - @Override - public void run() { - String prefix = Message.PREFIX.getMessage(); - player.sendMessage(prefix - + "§cYou do not use the latest version of the plugin! Thank you for taking the latest version to avoid any risk of problem!"); - player.sendMessage(prefix + "§fDownload plugin here: §a" + String.format(URL_RESOURCE, pluginID)); - } - }.runTaskLater(plugin, 20 * 2); - } - } - - /** - * Get version by plugin id - * - * @param consumer - * - Do something after - */ - public void getVersion(Consumer consumer) { - Bukkit.getScheduler().runTaskAsynchronously(this.plugin, () -> { - final String apiURL = String.format(URL_API, this.pluginID); - try { - URL url = new URL(apiURL); - URLConnection hc = url.openConnection(); - hc.setRequestProperty("User-Agent", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.4; en-US; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2"); - Scanner scanner = new Scanner(hc.getInputStream()); - if (scanner.hasNext()) - consumer.accept(scanner.next()); - scanner.close(); - - } catch (IOException exception) { - this.plugin.getLogger().info("Cannot look for updates: " + exception.getMessage()); - } - }); - } - -} diff --git a/src/main/java/fr/maxlego08/items/zcore/utils/storage/DiscUtils.java b/src/main/java/fr/maxlego08/items/zcore/utils/storage/DiscUtils.java index eb12287..aa9c99f 100644 --- a/src/main/java/fr/maxlego08/items/zcore/utils/storage/DiscUtils.java +++ b/src/main/java/fr/maxlego08/items/zcore/utils/storage/DiscUtils.java @@ -4,6 +4,8 @@ import java.net.URL; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; +import java.util.logging.Level; +import java.util.logging.Logger; public class DiscUtils { @@ -12,6 +14,7 @@ public class DiscUtils { // -------------------------------------------- // private final static String UTF8 = "UTF-8"; + private static final Logger LOGGER = Logger.getLogger(DiscUtils.class.getName()); // -------------------------------------------- // // BYTE @@ -81,7 +84,7 @@ public static boolean downloadUrl(String urlstring, File file) { fos.getChannel().transferFrom(rbc, 0, 1 << 24); return true; } catch (Exception e) { - e.printStackTrace(); + LOGGER.log(Level.SEVERE, "Error in DiscUtils operation", e); return false; } } @@ -114,7 +117,7 @@ public static byte[] utf8(String string) { try { return string.getBytes(UTF8); } catch (UnsupportedEncodingException e) { - e.printStackTrace(); + LOGGER.log(Level.SEVERE, "Failed to encode string to UTF-8", e); return null; } } @@ -123,7 +126,7 @@ public static String utf8(byte[] bytes) { try { return new String(bytes, UTF8); } catch (UnsupportedEncodingException e) { - e.printStackTrace(); + LOGGER.log(Level.SEVERE, "Failed to decode UTF-8 bytes to string", e); return null; } } diff --git a/src/main/java/fr/maxlego08/items/zcore/utils/storage/Persist.java b/src/main/java/fr/maxlego08/items/zcore/utils/storage/Persist.java index 95cae1f..efe6723 100644 --- a/src/main/java/fr/maxlego08/items/zcore/utils/storage/Persist.java +++ b/src/main/java/fr/maxlego08/items/zcore/utils/storage/Persist.java @@ -1,12 +1,12 @@ package fr.maxlego08.items.zcore.utils.storage; import fr.maxlego08.items.zcore.ZPlugin; -import fr.maxlego08.items.zcore.enums.Folder; import fr.maxlego08.items.zcore.logger.Logger; import fr.maxlego08.items.zcore.utils.ZUtils; import java.io.File; import java.lang.reflect.Type; +import java.util.logging.Level; public class Persist extends ZUtils { @@ -62,10 +62,6 @@ public T loadOrSaveDefault(T def, Class clazz, String name) { return loadOrSaveDefault(def, clazz, getFile(name)); } - public T loadOrSaveDefault(T def, Class clazz, Folder folder, String name) { - return loadOrSaveDefault(def, clazz, getFile(folder.toFolder() + File.separator + name)); - } - public T loadOrSaveDefault(T def, Class clazz, File file) { if (!file.exists()) { p.getLog().log("Creating default: " + file, Logger.LogType.SUCCESS); @@ -109,10 +105,6 @@ public boolean save(Object instance, String name) { return save(instance, getFile(name)); } - public boolean save(Object instance, Folder folder, String name) { - return save(instance, getFile(folder.toFolder() + File.separator + name)); - } - public boolean save(Object instance, File file) { try { @@ -123,8 +115,7 @@ public boolean save(Object instance, File file) { } catch (Exception e) { - p.getLog().log("cannot save files " + file.getAbsolutePath(), Logger.LogType.ERROR); - e.printStackTrace(); + p.getLogger().log(Level.SEVERE, "Failed to save file: " + file.getAbsolutePath(), e); return false; } diff --git a/src/main/java/fr/maxlego08/items/zcore/utils/yaml/YamlUtils.java b/src/main/java/fr/maxlego08/items/zcore/utils/yaml/YamlUtils.java deleted file mode 100644 index 073475c..0000000 --- a/src/main/java/fr/maxlego08/items/zcore/utils/yaml/YamlUtils.java +++ /dev/null @@ -1,100 +0,0 @@ -package fr.maxlego08.items.zcore.utils.yaml; - -import fr.maxlego08.items.zcore.logger.Logger; -import fr.maxlego08.items.zcore.utils.ZUtils; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.plugin.java.JavaPlugin; - -import java.io.File; - -/** - * Abstract utility class for handling YAML configuration files. - * Extends {@link ZUtils}. - */ -public abstract class YamlUtils extends ZUtils { - - protected transient final JavaPlugin plugin; - - /** - * Constructs a YamlUtils object with the specified plugin. - * - * @param plugin the JavaPlugin instance. - */ - public YamlUtils(JavaPlugin plugin) { - super(); - this.plugin = plugin; - } - - /** - * Gets the default configuration files of the plugin. - * - * @return the default FileConfiguration. - */ - protected FileConfiguration getConfig() { - return plugin.getConfig(); - } - - /** - * Loads a YAML configuration files. - * - * @param file the files to load. - * @return the YamlConfiguration of the files, or null if the files is null. - */ - protected YamlConfiguration getConfig(File file) { - if (file == null) { - return null; - } - return YamlConfiguration.loadConfiguration(file); - } - - /** - * Loads a YAML configuration files from a specified path. - * - * @param path the path to the configuration files. - * @return the YamlConfiguration of the files, or null if the files does not exist. - */ - protected YamlConfiguration getConfig(String path) { - File file = new File(plugin.getDataFolder() + "/" + path); - if (!file.exists()) { - return null; - } - return getConfig(file); - } - - /** - * Sends an informational message to the console. - * - * @param message the message to send. - */ - protected void info(String message) { - Logger.info(message); - } - - /** - * Sends a success message to the console. - * - * @param message the message to send. - */ - protected void success(String message) { - Logger.info(message, Logger.LogType.SUCCESS); - } - - /** - * Sends an error message to the console. - * - * @param message the message to send. - */ - protected void error(String message) { - Logger.info(message, Logger.LogType.ERROR); - } - - /** - * Sends a warning message to the console. - * - * @param message the message to send. - */ - protected void warn(String message) { - Logger.info(message, Logger.LogType.WARNING); - } -} diff --git a/src/main/resources/runes/infinite-bucket.yml b/src/main/resources/runes/infinite-bucket.yml new file mode 100644 index 0000000..101b8aa --- /dev/null +++ b/src/main/resources/runes/infinite-bucket.yml @@ -0,0 +1,23 @@ +# Type of the rune. +# "INFINITE_BUCKET" makes buckets infinite based on their current state: +# - Empty bucket: Stays empty forever (can pick up water/lava infinitely) +# - Water bucket: Never empties (infinite water source) +# - Lava bucket: Never empties (infinite lava source) +# - Other filled buckets: Never empty +type: INFINITE_BUCKET + +# Display name of the rune. +# This is the name that will appear when the player hovers over the item. +display-name: "ɪɴғɪɴɪᴛᴇ ʙᴜᴄᴋᴇᴛ" + +# Allowed materials for applying the rune. +# Only bucket types can have this rune applied. +# Reference: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Material.html +allowed-materials: + - BUCKET + - WATER_BUCKET + - LAVA_BUCKET + +# Allowed material tags for applying the rune. +# Empty since we're using specific materials above. +allowed-tags: [] \ No newline at end of file