diff --git a/pom.xml b/pom.xml index 158f581..34bb416 100644 --- a/pom.xml +++ b/pom.xml @@ -1,21 +1,27 @@ - - 4.0.0 - nl.pim16aap2 - ArmoredElytra - 2.4.8-SNAPSHOT + + 4.0.0 + nl.pim16aap2 + ArmoredElytra + 2.4.10 - - - spigot-repo - https://hub.spigotmc.org/nexus/content/repositories/snapshots/ - - - bstats-repo - http://repo.bstats.org/content/repositories/releases/ - - + + + spigot-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + CodeMC + https://repo.codemc.org/repository/maven-public + + + jitpack.io + https://jitpack.io + + - + org.spigotmc spigot @@ -23,24 +29,108 @@ provided - - - org.bstats - bstats-bukkit - 1.2 - - + + com.github.tr7zw + Item-NBT-API + master-SNAPSHOT + provided + - - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.8 - 1.8 - - - - + + + org.bstats + bstats-bukkit + 1.5 + compile + + + + + + + + ${project.name} + src + + + src + + **/*.java + + + + src/main/resources + true + + plugin.yml + config.yml + en_US.txt + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.1 + + + + org.bstats + nl.pim16aap2.armoredElytra + + + + + + + package + + shade + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 8 + 8 + + + + org.apache.maven.plugins + maven-antrun-plugin + 1.8 + + + deploy-artifact + package + + run + + + + + + + + + + + + + diff --git a/src/main/java/nl/pim16aap2/armoredElytra/ArmoredElytra.java b/src/main/java/nl/pim16aap2/armoredElytra/ArmoredElytra.java index 6f2adb1..e03a3fd 100644 --- a/src/main/java/nl/pim16aap2/armoredElytra/ArmoredElytra.java +++ b/src/main/java/nl/pim16aap2/armoredElytra/ArmoredElytra.java @@ -3,6 +3,7 @@ package nl.pim16aap2.armoredElytra; import java.util.Objects; import java.util.logging.Level; +import org.bstats.bukkit.Metrics; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.entity.Player; @@ -19,9 +20,13 @@ import nl.pim16aap2.armoredElytra.nbtEditor.NBTEditor; import nl.pim16aap2.armoredElytra.util.ArmorTier; import nl.pim16aap2.armoredElytra.util.ConfigLoader; import nl.pim16aap2.armoredElytra.util.Messages; -import nl.pim16aap2.armoredElytra.util.Metrics; import nl.pim16aap2.armoredElytra.util.Update; +// TODO: Use this for NBT stuff: https://www.spigotmc.org/resources/item-entity-tile-nbt-api.7939/ +// TODO: Figure out if the config really does read the list of enchantments accurately. A bug report with a customized config seemed to load the default settings... +// TODO: Verify enchantments on startup. Remove them from the list if they're invalid. +// TODO: Don't delete the config file. Look at BigDoors. + public class ArmoredElytra extends JavaPlugin implements Listener { private NBTEditor nbtEditor; @@ -151,6 +156,16 @@ public class ArmoredElytra extends JavaPlugin implements Listener elytraLore = (Objects.equals(elytraLore, new String("NONE")) ? null : elytraLore ); } + public boolean playerHasCraftPerm(Player player, ArmorTier armorTier) + { + return getConfigLoader().bypassCraftPerm() || player.hasPermission("armoredelytra.craft." + ArmorTier.getName(armorTier)); + } + + public boolean playerHasWearPerm(Player player, ArmorTier armorTier) + { + return getConfigLoader().bypassWearPerm() || player.hasPermission("armoredelytra.wear." + ArmorTier.getName(armorTier)); + } + // Returns true if this is the latest version of this plugin. public boolean isUpToDate() { diff --git a/src/main/java/nl/pim16aap2/armoredElytra/handlers/CommandHandler.java b/src/main/java/nl/pim16aap2/armoredElytra/handlers/CommandHandler.java index f5dcc16..02b4b6e 100644 --- a/src/main/java/nl/pim16aap2/armoredElytra/handlers/CommandHandler.java +++ b/src/main/java/nl/pim16aap2/armoredElytra/handlers/CommandHandler.java @@ -18,7 +18,7 @@ import nl.pim16aap2.armoredElytra.util.ArmorTier; public class CommandHandler implements CommandExecutor { private final ArmoredElytra plugin; - private final NBTEditor nbtEditor; + private final NBTEditor nbtEditor; public CommandHandler(ArmoredElytra plugin, NBTEditor nbtEditor) { diff --git a/src/main/java/nl/pim16aap2/armoredElytra/handlers/EventHandlers.java b/src/main/java/nl/pim16aap2/armoredElytra/handlers/EventHandlers.java index 3649ae0..c7f38bb 100644 --- a/src/main/java/nl/pim16aap2/armoredElytra/handlers/EventHandlers.java +++ b/src/main/java/nl/pim16aap2/armoredElytra/handlers/EventHandlers.java @@ -1,10 +1,13 @@ package nl.pim16aap2.armoredElytra.handlers; +import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Random; +import java.util.function.Consumer; import java.util.logging.Level; +import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Player; @@ -13,14 +16,16 @@ import org.bukkit.event.Listener; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityDamageEvent.DamageCause; import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.inventory.InventoryType; import org.bukkit.event.inventory.PrepareAnvilEvent; -import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.AnvilInventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.EnchantmentStorageMeta; -import org.bukkit.scheduler.BukkitRunnable; + +import com.codingforcookies.armorequip.ArmorEquipEvent; +import com.codingforcookies.armorequip.ArmorListener; +import com.codingforcookies.armorequip.ArmorType; +import com.codingforcookies.armorequip.DispenserArmorListener; import nl.pim16aap2.armoredElytra.ArmoredElytra; import nl.pim16aap2.armoredElytra.nbtEditor.NBTEditor; @@ -32,48 +37,74 @@ import nl.pim16aap2.armoredElytra.util.XMaterial; public class EventHandlers implements Listener { - private final NBTEditor nbtEditor; - private final ArmoredElytra plugin; - private final boolean is1_9; + private final NBTEditor nbtEditor; + private final ArmoredElytra plugin; + + private final Consumer cleanAnvilInventory; + private final Consumer moveChestplateToInventory; public EventHandlers(ArmoredElytra plugin, NBTEditor nbtEditor, boolean is1_9) { - this.plugin = plugin; + this.plugin = plugin; this.nbtEditor = nbtEditor; - this.is1_9 = is1_9; - } - - // Remove item from player's chestplate slot and puts it in their normal inventory. - private void unenquipChestPlayer(Player p) - { - if (is1_9) - p.getInventory().getChestplate().setType(XMaterial.AIR.parseMaterial()); - else - { - p.getInventory().addItem(p.getInventory().getChestplate()); - p.getInventory().getChestplate().setAmount(0); - p.updateInventory(); - } - } - - // Clear the anvil's inventory (destroy all the items in all 3 slots (second slot is not emptied, when repairing you can safely give multiple items)). - private void cleanAnvil(AnvilInventory anvilInventory) - { + initializeArmorEquipEvent(); if (is1_9) { - ItemStack air = new ItemStack(XMaterial.AIR.parseMaterial(), 1); - anvilInventory.setItem(0, air); - anvilInventory.setItem(1, air); - anvilInventory.setItem(2, air); + cleanAnvilInventory = this::cleanAnvilOld; + moveChestplateToInventory = this::moveChestplateToInventoryOld; } else { - anvilInventory.getItem(0).setAmount(0); - anvilInventory.getItem(1).setAmount(anvilInventory.getItem(1).getAmount() - 1); - anvilInventory.getItem(2).setAmount(0); + cleanAnvilInventory = this::cleanAnvilNew; + moveChestplateToInventory = this::moveChestplateToInventoryNew; } } + private void initializeArmorEquipEvent() + { + Bukkit.getPluginManager().registerEvents(new ArmorListener(new ArrayList()), plugin); + try + { + // Older versions did not have this event. So try to load it if possible, but + // don't worry + // about it not working. + Class.forName("org.bukkit.event.block.BlockDispenseArmorEvent"); + plugin.getServer().getPluginManager().registerEvents(new DispenserArmorListener(), plugin); + } + catch (Exception ignored) + { + } + } + + private void moveChestplateToInventoryOld(Player player) + { + player.getInventory().getChestplate().setType(XMaterial.AIR.parseMaterial()); + } + + private void moveChestplateToInventoryNew(Player player) + { + player.getInventory().addItem(player.getInventory().getChestplate()); + player.getInventory().getChestplate().setAmount(0); + player.updateInventory(); + } + + // Clean 1.9 inventories. + private void cleanAnvilOld(AnvilInventory anvilInventory) + { + ItemStack air = new ItemStack(XMaterial.AIR.parseMaterial(), 1); + anvilInventory.setItem(0, air); + anvilInventory.setItem(1, air); + anvilInventory.setItem(2, air); + } + + // Clean >=1.10 inventories. + private void cleanAnvilNew(AnvilInventory anvilInventory) + { + anvilInventory.getItem(0).setAmount(0); + anvilInventory.getItem(1).setAmount(anvilInventory.getItem(1).getAmount() - 1); + anvilInventory.getItem(2).setAmount(0); + } + // Check if the enchantment is allowed on elytras. private boolean isAllowedEnchantment(Enchantment enchant) { @@ -85,18 +116,20 @@ public class EventHandlers implements Listener } // Combine 2 maps of enchantments (and remove any invalid ones). - private Map combineEnchantments(Map enchantments0, Map enchantments1) + private Map combineEnchantments(Map enchantments0, + Map enchantments1) { enchantments0 = fixEnchantments(enchantments0); Map combined = new HashMap<>(fixEnchantments(enchantments0)); - // If the second set of enchantments is null, the combined enchantments are just the first enchantments. + // If the second set of enchantments is null, the combined enchantments are just + // the first enchantments. if (enchantments1 == null) return combined; - enchantments1 = fixEnchantments(enchantments1); + enchantments1 = fixEnchantments(enchantments1); // Loop through the enchantments of item1. - for (Map.Entry entry : enchantments1.entrySet()) + for (Map.Entry entry : enchantments1.entrySet()) { Integer enchantLevel = enchantments0.get(entry.getKey()); if (enchantLevel != null) @@ -123,11 +156,13 @@ public class EventHandlers implements Listener int protVal0 = Util.getProtectionEnchantmentsVal(enchantments0); int protVal1 = Util.getProtectionEnchantmentsVal(enchantments1); - // If they have different protection enchantments, keep enchantment1's enchantments - // And remove the protection enchantment from enchantments0. Yes, this system only works + // If they have different protection enchantments, keep enchantment1's + // enchantments + // And remove the protection enchantment from enchantments0. Yes, this system + // only works // If there is 1 protection enchantment on if (protVal0 != 0 && protVal1 != 0 && protVal0 != protVal1) - switch(protVal0) + switch (protVal0) { case 1: combined.remove(Enchantment.PROTECTION_ENVIRONMENTAL); @@ -154,7 +189,7 @@ public class EventHandlers implements Listener { // Get the multiplier for the repair items. double mult = 0.01; - if ( repairItem.getType().equals(Material.LEATHER)) + if (repairItem.getType().equals(Material.LEATHER)) mult *= (100.0f / plugin.getConfigLoader().LEATHER_TO_FULL()); else if (repairItem.getType().equals(Material.GOLD_INGOT)) @@ -191,18 +226,16 @@ public class EventHandlers implements Listener return ret; } - /* - * Valid inputs: - * - Elytra (armored or not) + chestplate -> Create Armored Elytra - * - Elytra (armored) + enchanted book -> Enchant - * - Elytra (armored) + its repair item -> Repair - * - Elytra (armored) + other elytra (armored) -> Combine (Enchant + Repair) - * ! Elytra (armored, !leather) + leather -> Block - * - * Ignoring: - * - Elytra (not armored) + !chestplate -> None - * - * + * -> None - */ + // Valid inputs: + // - Elytra (armored or not) + chestplate -> Create Armored Elytra + // - Elytra (armored) + enchanted book -> Enchant + // - Elytra (armored) + its repair item -> Repair + // - Elytra (armored) + other elytra (armored) -> Combine (Enchant + Repair) + // ! Elytra (armored, !leather) + leather -> Block + // + // Ignoring: + // - Elytra (not armored) + !chestplate -> None + // - * + * -> None private Action isValidInput(ItemStack itemOne, ItemStack itemTwo) { if (itemOne == null || itemTwo == null) @@ -212,8 +245,8 @@ public class EventHandlers implements Listener if (itemTwo.getType() == Material.ELYTRA && itemOne.getType() != Material.ELYTRA) { ItemStack tmp = itemOne; - itemOne = itemTwo; - itemTwo = tmp; + itemOne = itemTwo; + itemTwo = tmp; } if (itemOne.getType() != Material.ELYTRA) @@ -237,7 +270,8 @@ public class EventHandlers implements Listener if (ArmorTier.getRepairItem(tier) == matTwo) return Action.REPAIR; - // If the armored elytra is to be combined with another armored elytra of the same tier... + // If the armored elytra is to be combined with another armored elytra of the + // same tier... if (nbtEditor.getArmorTier(itemTwo) == tier) return Action.COMBINE; @@ -254,9 +288,9 @@ public class EventHandlers implements Listener @EventHandler private void onAnvilInventoryOpen(PrepareAnvilEvent event) { - Player player = (Player) event.getView().getPlayer(); - ItemStack itemA = event.getInventory().getItem(0); - ItemStack itemB = event.getInventory().getItem(1); + Player player = (Player) event.getView().getPlayer(); + ItemStack itemA = event.getInventory().getItem(0); + ItemStack itemB = event.getInventory().getItem(1); ItemStack result = null; if (itemA != null && itemB != null) @@ -264,42 +298,43 @@ public class EventHandlers implements Listener if (itemB.getType() == Material.ELYTRA && itemA.getType() != Material.ELYTRA) { result = itemA; - itemA = itemB; - itemB = result; + itemA = itemB; + itemB = result; result = null; } // Check if there are items in both input slots. if (itemA != null && itemB != null) { - Action action = isValidInput(itemA, itemB); - ArmorTier newTier = ArmorTier.NONE; - ArmorTier curTier = nbtEditor.getArmorTier(itemA); - short durability = 0; + Action action = isValidInput(itemA, itemB); + ArmorTier newTier = ArmorTier.NONE; + ArmorTier curTier = nbtEditor.getArmorTier(itemA); + short durability = 0; Map enchantments = itemA.getEnchantments(); - enchantments = fixEnchantments(enchantments); + enchantments = fixEnchantments(enchantments); switch (action) { case REPAIR: - newTier = curTier; - durability = repairItem(itemA.getDurability(), itemB); + newTier = curTier; + durability = repairItem(itemA.getDurability(), itemB); break; case COMBINE: - newTier = curTier; - durability = (short) (- itemA.getType().getMaxDurability() - itemA.getDurability() - itemB.getDurability()); - durability = durability < 0 ? 0 : durability; - enchantments = combineEnchantments(enchantments, itemB.getEnchantments()); + newTier = curTier; + durability = (short) (-itemA.getType().getMaxDurability() - itemA.getDurability() + - itemB.getDurability()); + durability = durability < 0 ? 0 : durability; + enchantments = combineEnchantments(enchantments, itemB.getEnchantments()); break; case CREATE: - newTier = Util.armorToTier(itemB.getType()); - durability = 0; - enchantments = combineEnchantments(enchantments, itemB.getEnchantments()); + newTier = Util.armorToTier(itemB.getType()); + durability = 0; + enchantments = combineEnchantments(enchantments, itemB.getEnchantments()); break; case ENCHANT: EnchantmentStorageMeta meta = (EnchantmentStorageMeta) itemB.getItemMeta(); - newTier = curTier; - durability = itemA.getDurability(); + newTier = curTier; + durability = itemA.getDurability(); // If there aren't any illegal enchantments on the book, continue as normal. // Otherwise... Block. if (verifyEnchantments(meta.getStoredEnchants()) != meta.getStoredEnchants().size()) @@ -316,7 +351,7 @@ public class EventHandlers implements Listener return; } - if (Util.playerHasCraftPerm(player, newTier)) + if (plugin.playerHasCraftPerm(player, newTier)) { result = new ItemStack(Material.ELYTRA, 1); if (enchantments != null) @@ -329,8 +364,10 @@ public class EventHandlers implements Listener } // Check if either itemA or itemB is unoccupied. - if ((itemA == null || itemB == null) && nbtEditor.getArmorTier(event.getInventory().getItem(2)) != ArmorTier.NONE) - // If Item2 is occupied despite itemA or itemB not being occupied. (only for armored elytra)/ + if ((itemA == null || itemB == null) && + nbtEditor.getArmorTier(event.getInventory().getItem(2)) != ArmorTier.NONE) + // If Item2 is occupied despite itemA or itemB not being occupied. (only for + // armored elytra)/ event.setResult(null); player.updateInventory(); } @@ -357,22 +394,28 @@ public class EventHandlers implements Listener } catch (ClassCastException exception) { - // Print warning to console and exit onInventoryClick event (no support for custom inventories as they are usually used for GUI's). - plugin.debugMsg(Level.WARNING, "Could not cast inventory to anvilInventory for player " + player.getName() + "! Armored Elytras cannot be crafted!"); + // Print warning to console and exit onInventoryClick event (no support for + // custom inventories as they are usually used for GUI's). + plugin.debugMsg(Level.WARNING, "Could not cast inventory to anvilInventory for player " + player.getName() + + "! Armored Elytras cannot be crafted!"); return; } int slot = e.getRawSlot(); - if (slot == 2 && anvilInventory.getItem(0) != null && anvilInventory.getItem(1) != null && anvilInventory.getItem(2) != null) + if (slot == 2 && anvilInventory.getItem(0) != null && anvilInventory.getItem(1) != null && + anvilInventory.getItem(2) != null) { ArmorTier armortier = nbtEditor.getArmorTier(anvilInventory.getItem(2)); // If there's an armored elytra in the final slot... - if (armortier != ArmorTier.NONE && Util.playerHasCraftPerm(player, armortier)) + if (armortier != ArmorTier.NONE && plugin.playerHasCraftPerm(player, armortier)) { - // Create a new armored elytra and give that one to the player instead of the result. - // This is done because after putting item0 in AFTER item1, the first letter of the color code shows up, this gets rid of that problem. - ItemStack result = nbtEditor.addArmorNBTTags(anvilInventory.getItem(2), armortier, plugin.getConfigLoader().unbreakable()); + // Create a new armored elytra and give that one to the player instead of the + // result. + // This is done because after putting item0 in AFTER item1, the first letter of + // the color code shows up, this gets rid of that problem. + ItemStack result = nbtEditor.addArmorNBTTags(anvilInventory.getItem(2), armortier, + plugin.getConfigLoader().unbreakable()); // Give the result to the player and clear the anvil's inventory. if (e.isShiftClick()) { @@ -384,98 +427,34 @@ public class EventHandlers implements Listener else player.setItemOnCursor(result); // Clean the anvil's inventory after transferring the items. - cleanAnvil(anvilInventory); + cleanAnvilInventory.accept(anvilInventory); player.updateInventory(); return; } } } - // Make sure the player has the correct permission and that the item is not broken. + // Make sure the player has the correct permission and that the item is not + // broken. private AllowedToWearEnum isAllowedToWear(ItemStack elytra, Player player, ArmorTier armorTier) { + if (armorTier.equals(ArmorTier.NONE)) + return AllowedToWearEnum.ALLOWED; if (Util.isBroken(elytra)) return AllowedToWearEnum.BROKEN; - if (!Util.playerHasWearPerm(player, armorTier)) + if (!plugin.playerHasWearPerm(player, armorTier)) return AllowedToWearEnum.NOPERMISSION; return AllowedToWearEnum.ALLOWED; } - // Check if the player tries to equip armor by richt clicking it. - @EventHandler - public void onRightClick(PlayerInteractEvent event) - { - if (!event.getAction().equals(org.bukkit.event.block.Action.RIGHT_CLICK_AIR) && - !event.getAction().equals(org.bukkit.event.block.Action.RIGHT_CLICK_BLOCK)) - return; - - ItemStack item = event.getItem(); - if (item == null) - return; - - Player player = event.getPlayer(); - - if (item.getType() == Material.ELYTRA) - { - ArmorTier armorTier = nbtEditor.getArmorTier(item); - if (nbtEditor.getArmorTier(item) == ArmorTier.NONE) - return; - AllowedToWearEnum allowed = isAllowedToWear(item, player, armorTier); - if (allowed == AllowedToWearEnum.BROKEN) - { - plugin.messagePlayer(player, plugin.getMyMessages().getString("MESSAGES.RepairNeeded")); - event.setCancelled(true); - player.updateInventory(); - } - else if (allowed == AllowedToWearEnum.NOPERMISSION) - { - plugin.usageDeniedMessage(player, armorTier); - event.setCancelled(true); - player.updateInventory(); - } - } - } - - // Player closes their inventory. Also checks for whether they are allowed to wear the armored elytra they are wearing. - // This is done again here because there are ways to bypass permission check when equipping. - @EventHandler - public void onInventoryClose(InventoryCloseEvent e) - { - verifyArmorInChestSlot((Player) e.getPlayer()); - } - - // Check if the player is allowed to wear the armored elytra based on their permissions. - private void verifyArmorInChestSlot(Player player) - { - ItemStack chestplate = player.getInventory().getChestplate(); - // If the player equips a new chestplate. - if (player.getInventory().getChestplate() == null) - return; - - ArmorTier armorTier = nbtEditor.getArmorTier(chestplate); - // If that chestplate is an (armored) elytra. - if (armorTier == ArmorTier.NONE) - return; - - AllowedToWearEnum allowed = isAllowedToWear(chestplate, player, armorTier); - if (allowed == AllowedToWearEnum.BROKEN) - { - plugin.messagePlayer(player, plugin.getMyMessages().getString("MESSAGES.RepairNeeded")); - unenquipChestPlayer(player); - } - else if (allowed == AllowedToWearEnum.NOPERMISSION) - { - plugin.usageDeniedMessage(player, armorTier); - unenquipChestPlayer(player); - } - } - - // Because the armored elytra doesn't actually give any armor, the damage - // received by players wearing an armored elytra is calculated here. + // Handle armored elytra durability loss. @EventHandler(ignoreCancelled = true) public void onPlayerDamage(EntityDamageEvent e) { - if(!(e.getEntity() instanceof Player)) + if (!(e.getEntity() instanceof Player)) + return; + + if (plugin.getConfigLoader().unbreakable()) return; Player p = (Player) e.getEntity(); @@ -495,21 +474,24 @@ public class EventHandlers implements Listener if (cause != DamageCause.DROWNING && cause != DamageCause.STARVATION && cause != DamageCause.SUFFOCATION && cause != DamageCause.SUICIDE && cause != DamageCause.FLY_INTO_WALL && cause != DamageCause.POISON) { - int durability = p.getInventory().getChestplate().getDurability(); + int durability = p.getInventory().getChestplate().getDurability(); int maxDurability = p.getInventory().getChestplate().getType().getMaxDurability(); int newDurability = durability + ((int) (e.getDamage() / 4) > 1 ? (int) (e.getDamage() / 4) : 1); - // If the elytra has the durability enchantment, we calculate the durability loss ourselves. + // If the elytra has the durability enchantment, we calculate the durability + // loss ourselves. if (p.getInventory().getChestplate().containsEnchantment(Enchantment.DURABILITY)) { - // Get a random int between 0 and 100 to use in deciding if the durability enchantment will take effect. - Random r = new Random(); - int randomInt = r.nextInt(101); - int enchantLevel = p.getInventory().getChestplate().getEnchantmentLevel(Enchantment.DURABILITY); + // Get a random int between 0 and 100 to use in deciding if the durability + // enchantment will take effect. + Random r = new Random(); + int randomInt = r.nextInt(101); + int enchantLevel = p.getInventory().getChestplate().getEnchantmentLevel(Enchantment.DURABILITY); int durabilityDelta = (100 / (enchantLevel + 1)) < randomInt ? 0 : 1; - // If the durability equals/exceeds maxDurability, it's broken (0 = full item durability). + // If the durability equals/exceeds maxDurability, it's broken (0 = full item + // durability). if (durability >= maxDurability) - unenquipChestPlayer(p); + moveChestplateToInventory.accept(p); else newDurability = durability + durabilityDelta; } @@ -517,28 +499,37 @@ public class EventHandlers implements Listener if (newDurability >= maxDurability) { newDurability = maxDurability; - unenquipChestPlayer(p); + moveChestplateToInventory.accept(p); } elytra.setDurability((short) (newDurability)); } } } - // Check if the player is trying to equip a broken elytra (and prevent that). @EventHandler - public void playerEquipsArmor(InventoryClickEvent e) + public void onEquip(ArmorEquipEvent e) { - if (!(e.getWhoClicked() instanceof Player)) + if (!e.getType().equals(ArmorType.CHESTPLATE) || + !e.getNewArmorPiece().getType().equals(Material.ELYTRA) || + e.getNewArmorPiece() == null) return; - Player player = (Player) e.getWhoClicked(); - new BukkitRunnable() + ArmorTier armorTier = nbtEditor.getArmorTier(e.getNewArmorPiece()); + AllowedToWearEnum allowed = isAllowedToWear(e.getNewArmorPiece(), e.getPlayer(), armorTier); + switch(allowed) { - @Override - public void run() - { - verifyArmorInChestSlot(player); - } - }.runTaskLater(plugin, 1); + case ALLOWED: + break; + case BROKEN: + plugin.messagePlayer(e.getPlayer(), plugin.getMyMessages().getString("MESSAGES.RepairNeeded")); + e.setCancelled(true); + break; + case NOPERMISSION: + plugin.usageDeniedMessage(e.getPlayer(), armorTier); + e.setCancelled(true); + break; + default: + break; + } } } \ No newline at end of file diff --git a/src/main/java/nl/pim16aap2/armoredElytra/nbtEditor/GetArmorValueNew.java b/src/main/java/nl/pim16aap2/armoredElytra/nbtEditor/GetArmorValueNew.java index 8d9d6cf..91b8bdc 100644 --- a/src/main/java/nl/pim16aap2/armoredElytra/nbtEditor/GetArmorValueNew.java +++ b/src/main/java/nl/pim16aap2/armoredElytra/nbtEditor/GetArmorValueNew.java @@ -28,7 +28,6 @@ public class GetArmorValueNew implements GetArmorValue plugin.myLogger(Level.INFO, "Failed to obtain armor value from NBT!"); return 0; } - else - return 0; + return 0; } } diff --git a/src/main/java/nl/pim16aap2/armoredElytra/nbtEditor/GetArmorValueOld.java b/src/main/java/nl/pim16aap2/armoredElytra/nbtEditor/GetArmorValueOld.java index 29d4e97..a2bad40 100644 --- a/src/main/java/nl/pim16aap2/armoredElytra/nbtEditor/GetArmorValueOld.java +++ b/src/main/java/nl/pim16aap2/armoredElytra/nbtEditor/GetArmorValueOld.java @@ -21,7 +21,6 @@ public class GetArmorValueOld implements GetArmorValue String stringAtPos = nbtString.substring(pos, pos + 1); return Integer.parseInt(stringAtPos); } - else - return 0; + return 0; } } diff --git a/src/main/java/nl/pim16aap2/armoredElytra/nbtEditor/NBTEditor.java b/src/main/java/nl/pim16aap2/armoredElytra/nbtEditor/NBTEditor.java index 8c95042..e69617e 100644 --- a/src/main/java/nl/pim16aap2/armoredElytra/nbtEditor/NBTEditor.java +++ b/src/main/java/nl/pim16aap2/armoredElytra/nbtEditor/NBTEditor.java @@ -163,7 +163,7 @@ public class NBTEditor Object nmsStack = asNMSCopy.invoke(null, item); Object compound = ((boolean) hasTag.invoke(nmsStack) ? getTag.invoke(nmsStack) : NBTTagCompound.newInstance()); Object modifiers = NBTTagList.newInstance(); - Object armor = NBTTagCompound.newInstance(); + Object armor = NBTTagCompound.newInstance(); // I should be able to simply add custom tags here! setTag.invoke (armor, "AttributeName", NBTTagStringCtor.newInstance("generic.armor")); setTag.invoke (armor, "Name", NBTTagStringCtor.newInstance("generic.armor")); setTag.invoke (armor, "Amount", NBTTagIntCtor.newInstance(armorProtection)); diff --git a/src/main/java/nl/pim16aap2/armoredElytra/util/ConfigLoader.java b/src/main/java/nl/pim16aap2/armoredElytra/util/ConfigLoader.java index c7dac1a..8294da0 100644 --- a/src/main/java/nl/pim16aap2/armoredElytra/util/ConfigLoader.java +++ b/src/main/java/nl/pim16aap2/armoredElytra/util/ConfigLoader.java @@ -30,6 +30,8 @@ public class ConfigLoader private boolean noFlightDurability; private List allowedEnchantments; private boolean allowMultipleProtectionEnchantments; + public boolean bypassWearPerm; + public boolean bypassCraftPerm; private ArrayList> configOptionsList; private ArmoredElytra plugin; @@ -87,7 +89,7 @@ public class ConfigLoader "Setting this to true will disable this plugin and remove any armored elytras it can find.", "It will check player's inventories and their end chest upon login and any regular chest when it is opened.", "This means it will take a while for all armored elytras to be removed from your server, but it doesn't take up ", - "a lot of resources, so you can just leave it installed and ignore it.", + "a lot of resources, so you can just leave the plugin enabled and ignore it.", "Please do not forget to MAKE A BACKUP before enabling this option!" }; String[] languageFileComment = @@ -102,11 +104,16 @@ public class ConfigLoader "elytra that has the protection enchantment with an enchanted book that has the blast protection enchantment", "would result in removal of the protection enchantment and addition of the blast protection enchantment." }; + String[] permissionsComment = + { + "Globally bypass permissions for wearing and/or crafting amored elytras. Useful if permissions are unavailable." + }; + // Set default list of allowed enchantments. allowedEnchantments = new ArrayList<>(Arrays.asList("DURABILITY", "PROTECTION_FIRE", "PROTECTION_EXPLOSIONS", "PROTECTION_PROJECTILE", "PROTECTION_ENVIRONMENTAL", "THORNS", - "BINDING_CURSE", "VANISHING_CURSE")); + "BINDING_CURSE", "VANISHING_CURSE", "MENDING")); FileConfiguration config = plugin.getConfig(); @@ -123,6 +130,8 @@ public class ConfigLoader enableDebug = addNewConfigOption(config, "enableDebug", false, debugComment); uninstallMode = addNewConfigOption(config, "uninstallMode", false, uninstallComment); languageFile = addNewConfigOption(config, "languageFile", "en_US", languageFileComment); + bypassWearPerm = addNewConfigOption(config, "bypassWearPermissions", true, permissionsComment); + bypassCraftPerm = addNewConfigOption(config, "bypassCraftPermissions", true, null); writeConfig(); } @@ -239,4 +248,14 @@ public class ConfigLoader { return allowedEnchantments; } + + public boolean bypassWearPerm() + { + return bypassWearPerm; + } + + public boolean bypassCraftPerm() + { + return bypassCraftPerm; + } } diff --git a/src/main/java/nl/pim16aap2/armoredElytra/util/Messages.java b/src/main/java/nl/pim16aap2/armoredElytra/util/Messages.java index 617f515..de05f30 100644 --- a/src/main/java/nl/pim16aap2/armoredElytra/util/Messages.java +++ b/src/main/java/nl/pim16aap2/armoredElytra/util/Messages.java @@ -37,7 +37,11 @@ public class Messages // Load the default en_US from the resources. plugin.saveResource("en_US.txt", true); - defaultFile.setWritable(false); + if (!defaultFile.setWritable(false)) + { + plugin.myLogger(Level.WARNING, "Could not set default language file to read-only!"); + plugin.myLogger(Level.WARNING, "This is not a big problem. Just remember not to edit the file!"); + } } // Read locale file. @@ -58,7 +62,7 @@ public class Messages String[] parts = sCurrentLine.split("=", 2); key = parts[0]; value = parts[1].replaceAll("&((?i)[0-9a-fk-or])", "\u00A7$1"); - String[] newLineSplitter = value.split("\\\\n"); // Wut? Can I haz more backslash? + String[] newLineSplitter = value.split("\\\\n"); String values = newLineSplitter[0]; @@ -83,22 +87,11 @@ public class Messages // Get a string from a key. Returns "null" if null. public String getString(String key) { - String value = null; - value = messageMap.get(key); - if (value == null) - { - value = "ArmoredElytra: Translation not found! Contact server admin!"; - plugin.myLogger(Level.WARNING, "Failed to get translation for key " + key); - } - return value; - } + String value = messageMap.get(key); + if (value != null) + return value; - public String getStringReverse(String value) - { - return messageMap.entrySet().stream() - .filter(e -> e.getValue().equals(value)) - .map(Map.Entry::getKey) - .findFirst() - .orElse(null); + plugin.myLogger(Level.WARNING, "Failed to get the translation for key " + key); + return "Translation for key \"" + key + "\" not found! Contact server admin!"; } } diff --git a/src/main/java/nl/pim16aap2/armoredElytra/util/Metrics.java b/src/main/java/nl/pim16aap2/armoredElytra/util/Metrics.java deleted file mode 100644 index fc9ed85..0000000 --- a/src/main/java/nl/pim16aap2/armoredElytra/util/Metrics.java +++ /dev/null @@ -1,425 +0,0 @@ -package nl.pim16aap2.armoredElytra.util; - -import org.bukkit.Bukkit; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.plugin.RegisteredServiceProvider; -import org.bukkit.plugin.ServicePriority; -import org.bukkit.plugin.java.JavaPlugin; -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; - -import javax.net.ssl.HttpsURLConnection; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Timer; -import java.util.TimerTask; -import java.util.UUID; -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"; - - // Should failed requests be logged? - private static boolean logFailedRequests; - - // The uuid of the server - private static String serverUUID; - - // The plugin - private final JavaPlugin plugin; - - // A list with all custom charts - private final List charts = new ArrayList<>(); - - /** - * Class constructor. - * - * @param plugin The plugin which stats should be submitted. - */ - public Metrics(JavaPlugin plugin) - { - if (plugin == null) - { - throw new IllegalArgumentException("Plugin cannot be null!"); - } - this.plugin = plugin; - - // Get the config file - File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); - File configFile = new File(bStatsFolder, "config.yml"); - YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); - - // Check if the config file 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); - - // 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 - serverUUID = config.getString("serverUuid"); - logFailedRequests = config.getBoolean("logFailedRequests", false); - 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(); - } - } - - /** - * 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, new Runnable() - { - @Override - public void run() - { - 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. - */ - @SuppressWarnings("unchecked") - public JSONObject getPluginData() - { - JSONObject data = new JSONObject(); - - String pluginName = plugin.getDescription().getName(); - String pluginVersion = plugin.getDescription().getVersion(); - - data.put("pluginName", pluginName); // Append the name of the plugin - data.put("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.put("customCharts", customCharts); - - return data; - } - - /** - * Gets the server specific data. - * - * @return The server specific data. - */ - @SuppressWarnings("unchecked") - 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 = org.bukkit.Bukkit.getVersion(); - bukkitVersion = bukkitVersion.substring(bukkitVersion.indexOf("MC: ") + 4, bukkitVersion.length() - 1); - - // 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.put("serverUUID", serverUUID); - - data.put("playerAmount", playerAmount); - data.put("onlineMode", onlineMode); - data.put("bukkitVersion", bukkitVersion); - - data.put("javaVersion", javaVersion); - data.put("osName", osName); - data.put("osArch", osArch); - data.put("osVersion", osVersion); - data.put("coreCount", coreCount); - - return data; - } - - /** - * Collects the data and sends it afterwards. - */ - @SuppressWarnings("unchecked") - 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 - { - pluginData.add(provider.getService().getMethod("getPluginData").invoke(provider.getProvider())); - } - catch (NullPointerException | NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) - { - } - } - } - catch (NoSuchFieldException ignored) - { - } - } - - data.put("plugins", pluginData); - - // Create a new thread for the connection to the bStats server - new Thread(new Runnable() - { - @Override - public void run() - { - try - { - // Send the data - sendData(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 data The data to send. - * @throws Exception If the request failed. - */ - private static void sendData(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!"); - } - 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); - DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream()); - outputStream.write(compressedData); - outputStream.flush(); - outputStream.close(); - - connection.getInputStream().close(); // We don't care about the response - Just send our data :) - } - - /** - * 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(); - GZIPOutputStream gzip = new GZIPOutputStream(outputStream); - gzip.write(str.getBytes("UTF-8")); - gzip.close(); - 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; - } - - @SuppressWarnings("unchecked") - private JSONObject getRequestJsonObject() - { - JSONObject chart = new JSONObject(); - chart.put("chartId", chartId); - try - { - JSONObject data = getChartData(); - if (data == null) - { - // If the data is null we don't send the chart. - return null; - } - chart.put("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; - } -} diff --git a/src/main/java/nl/pim16aap2/armoredElytra/util/Util.java b/src/main/java/nl/pim16aap2/armoredElytra/util/Util.java index a5253f6..9d8369b 100644 --- a/src/main/java/nl/pim16aap2/armoredElytra/util/Util.java +++ b/src/main/java/nl/pim16aap2/armoredElytra/util/Util.java @@ -6,7 +6,6 @@ import java.util.Map; import org.bukkit.Material; import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; public class Util @@ -92,14 +91,4 @@ public class Util ret += 16; return ret; } - - public static boolean playerHasCraftPerm(Player player, ArmorTier armorTier) - { - return player.hasPermission("armoredelytra.craft." + ArmorTier.getName(armorTier)); - } - - public static boolean playerHasWearPerm(Player player, ArmorTier armorTier) - { - return player.hasPermission("armoredelytra.wear." + ArmorTier.getName(armorTier)); - } } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 864b40a..389dc4d 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ name: ArmoredElytra main: nl.pim16aap2.armoredElytra.ArmoredElytra -version: 2.4.8 +version: ${project.version} author: pim16aap2 commands: ArmoredElytra: