commit 70d7d7f6a0eb0e46a96505b1c2c36a5e715e245b Author: j502647092 Date: Wed Apr 29 10:04:46 2015 +0800 初始化项目... Signed-off-by: j502647092 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..92c7df5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +# Eclipse stuff +/.classpath +/.project +/.settings + +# netbeans +/nbproject + +# we use maven! +/build.xml + +# maven +/target +/repo + +# vim +.*.sw[a-p] + +# various other potential build files +/build +/bin +/dist +/manifest.mf + +/world + +# Mac filesystem dust +*.DS_Store + +# intellij +*.iml +*.ipr +*.iws +.idea/ + +# Project Stuff +/src/main/resources/Soulbound + +# Other Libraries +*.jar + +# Atlassian Stuff +/atlassian-ide-plugin.xml \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..fa9a495 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +[![Soulbound][Banner]][GitHub] +What is Soulbound? +---------------- + +Souldbound is a Bukkit plugin that makes it possible to have Soulbound items. +Whenever an item is soulbinded, players cannot transfer the item to other players. + +How does it work? +---------------- + +Check out the [BukkitDev]-page for more information! + + +Who made this? +---------------- +[![TfT_02](http://www.gravatar.com/avatar/b8914f9970e1f6ffd5281ce4770e20a7.png)](http://dev.bukkit.org/profiles/TfT_02/) + +Download here: [BukkitDev] + +[Banner]: https://dl.dropbox.com/u/29178507/Dev/Soulbound/title-banner.png +[BukkitDev]: http://dev.bukkit.org/server-mods/Soulbound/ +[GitHub]: https://github.com/TfT-02/Soulbound diff --git a/changelog.txt b/changelog.txt new file mode 100644 index 0000000..cbfe0d4 --- /dev/null +++ b/changelog.txt @@ -0,0 +1,94 @@ +Changelog: + +Key: + + Addition + = Fix + ! Change + - Removal + +Version 1.2.0 | Tested on CB 1.7.9-R0.1 + + Added support for giving Soulbinded kits to other players + + Added option to have Soulbind items without adding the player name + ! Changed the way UUIDs are handled, they are now hidden in the Soulbound lore :) + ! Changed "Bind on Use" also triggers when enchanting + +Version 1.1.9 | Tested on CB 1.7.5-R0.1 + ! Now using MythicDrops hook to support MythicDrops versions 1.1.2 through 3.0.0. Huge shout-out to Nunnery! + +Version 1.1.8 | Tested on CB 1.7.5-R0.1 + + Added new Items config file, define items with displaynames/lore here which will be Soulbound + + Added feature which allows you to Soulbind items when spawning in a kit + + Added feature which allows you to Soulbind items on respawn, useful for spawnkits. + + Added MythicDrops 1.1.2 AND 2.0.0 support. Thanks Nunnery! + + Added UUID support + +Version 1.1.7 | Tested on CB 1.6.4-R0.1 + - Removed old Updater class + +Version 1.1.6 | Tested on CB 1.6.4-R0.1 + ! Changed Updater, now uses Gravity's updater v2.0 + ! Changed notification messages when dependencies are found, they're hidden by default + +Version 1.1.5 | Tested on CB 1.6.2-R1.0 + + Added new config option; "Delete_On_Drop" + + Added new permission nodes soulbound.items.keep_on_death and soulbound.items.delete_on_death, both are false by default + = Fixed bug where soulbound items would disappear when dropping them by dragging them out of the inventory + = Fixed MythicDrops support, works with MythicDrops 1.1.2 + ! Changed config file system, files now automatically update + ! Changed "Allow_Item_Drop" to "Prevent_Item_Drop" + - Removed config options "Keep_On_Death" and "Delete_On_Death". Use permissions instead! + - Removed DiabloDrops support + +Version 1.1.4 | Tested on CB 1.5.2-R0.1 + + Added a new configuration option to prevent using certain commands when holding a Soulbound item + + Added a new configuration option to Soulbind the item in hand when a certain command is used. + + Added an option to disable the feedback messages "Inventory now Soulbound to.." + ! Hoppers can no longer pickup Soulbound items + = Fixed infinite durability not working for armor and special tools + +Version 1.1.3 | Tested on CB 1.5.0-R0.1 + + Added MythicDrops support + + Added permission node which allows certain players to pickup items which are not Soulbound to that player + + Added permission node which limit the maximum amount of Soulbound items a player can carry at once + + Added new configuration option to let Soulbound items have infinite durability + + Added new command to Soulbind a full inventory. /bind [player] inventory + = Fixed bug which caused the plugin to ignore cancelled SoulbindItemEvents + ! Improved the Bind on Equip mechanism even more, to account for recent inventory changes in MC 1.5 + +Version 1.1.2 | Tested on CB 1.4.7-R1.0 & 1.5.0-R0.1 + + Added help pages, access them using /soulbound help + = Fixed bug where DiabloDrops chests where never edited. Chest generated by DiabloDrops can now also contain Soulbound items + = Fixed bug where Chainmail armor and Skulls were never Binded on Equip + = Fixed bug which caused an NPE when trying to bind with nothing in hand + = Fixed bug which would throw an ArrayIndexOutOfBounds error + = Fixed bug which allowed player to Soulbind items twice + ! Improved the Bind on Equip mechanism! + +Version 1.1.1 | Tested on CB 1.4.7-R1.0 + + Added API functions so other plugins can hook into Soulbound as well! (Instead of the other way round) + + Added support for LoreLocks! + = Fixed bug with the Update Checker (I really did this time) + = Fixed bug with EpicBossRecoded when bosses had no loot defined + +Version 1.1.0 | Tested on CB 1.4.7-R1.0 + + Added support for DiabloDrops! Make Legendary items Soulbound on pickup, or make armor Sets Soulbound on Equip + + Added support for EpicBossRecoded! Drops from an EpicBoss can get flagged as "Bind on Pickup", "Bind on Equip" or "Bind on Use" + + Added new item type "Bind on Equip", when a player equips this item it will get Soulbound to that player + + Added new item type "Bind on Use", when a player uses (left/right click) this item it will get Soulbound to that player + ! Bind on Pickup, now also binds items when clicking them in containers. + = Fixed bug where /bindonpickup was broken + = Fixed bug where items could have multiple lines with "Soulbound" + +Version 1.0.1 | Tested on CB 1.4.7-R1.0 + = Fixed bug with the Update Checker + +Version 1.0.0 | Tested on CB 1.4.7-R1.0 + + First release + + Added Soulbound items + + Added Bind on Pickup items + + Added prevent storing Soulbound in containers + + Added prevent dropping Soulbound items + + Added prevent dropping Soulbound items on death + + Added keep Soulbound items after death + + Added Metrics for stats + + Added an update check diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..ff849ef --- /dev/null +++ b/pom.xml @@ -0,0 +1,101 @@ + + 4.0.0 + com.me.tft_02.soulbound + Soulbound + 1.1.10-SNAPSHOT + Soulbound + https://github.com/TfT-02/Soulbound + + GitHub + https://github.com/TfT-02/Soulbound/issues + + + Soulbound + ${basedir}/src/main/java + + + . + true + ${basedir}/src/ + + *.yml + .jenkins + + + + + + maven-compiler-plugin + 3.0 + + 1.6 + 1.6 + + + + org.apache.maven.plugins + maven-shade-plugin + 1.5 + + + + org.mcstats.bukkit:metrics + org.nunnerycode.bukkit:mythicdropsapi-lib + + + + + org.mcstats.bukkit + com.me.tft_02.soulbound.util + + + org.nunnerycode.bukkit + com.me.tft_02.soulbound.util + + + + + + package + + shade + + + + + + + + + spigot-repo + https://hub.spigotmc.org/nexus/content/groups/public/ + + + Plugin Metrics + http://repo.mcstats.org/content/repositories/public + + + mythicdrops-repo + http://repository-topplethenun.forge.cloudbees.com/snapshot/ + + + + + org.bukkit + bukkit + 1.8-R0.1-SNAPSHOT + jar + provided + + + org.mcstats.bukkit + metrics + R7 + compile + + + + UTF-8 + + diff --git a/src/main/assembly/package.xml b/src/main/assembly/package.xml new file mode 100644 index 0000000..30993fb --- /dev/null +++ b/src/main/assembly/package.xml @@ -0,0 +1,14 @@ + + bin + false + + zip + + + + ${project.build.directory}/${artifactId}.jar + / + Soulbound.jar + + + \ No newline at end of file diff --git a/src/main/java/com/me/tft_02/soulbound/Soulbound.java b/src/main/java/com/me/tft_02/soulbound/Soulbound.java new file mode 100644 index 0000000..6e20a58 --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/Soulbound.java @@ -0,0 +1,136 @@ +package com.me.tft_02.soulbound; + +import java.io.File; + +import net.gravitydevelopment.updater.soulbound.Updater; + +import org.bukkit.plugin.PluginManager; +import org.bukkit.plugin.java.JavaPlugin; + +import com.me.tft_02.soulbound.commands.BindCommand; +import com.me.tft_02.soulbound.commands.BindOnEquipCommand; +import com.me.tft_02.soulbound.commands.BindOnPickupCommand; +import com.me.tft_02.soulbound.commands.BindOnUseCommand; +import com.me.tft_02.soulbound.commands.SoulboundCommand; +import com.me.tft_02.soulbound.commands.UnbindCommand; +import com.me.tft_02.soulbound.config.Config; +import com.me.tft_02.soulbound.config.ItemsConfig; +import com.me.tft_02.soulbound.hooks.EpicBossRecodedListener; +import com.me.tft_02.soulbound.listeners.BlockListener; +import com.me.tft_02.soulbound.listeners.EntityListener; +import com.me.tft_02.soulbound.listeners.InventoryListener; +import com.me.tft_02.soulbound.listeners.PlayerListener; +import com.me.tft_02.soulbound.util.LogFilter; + +public class Soulbound extends JavaPlugin { + /* File Paths */ + private static String mainDirectory; + + public static Soulbound p; + + // Jar Stuff + public static File soulbound; + + // Checks for hooking into other plugins + public static boolean epicBossRecodedEnabled = false; + public static boolean loreLocksEnabled = false; + public static boolean mythicDropsEnabled = false; + + // Update Check + private boolean updateAvailable; + + /** + * Run things on enable. + */ + @Override + public void onEnable() { + p = this; + getLogger().setFilter(new LogFilter(this)); + + setupFilePaths(); + + loadConfigFiles(); + + setupEpicBossRecoded(); + + registerEvents(); + + getCommand("soulbound").setExecutor(new SoulboundCommand()); + getCommand("bind").setExecutor(new BindCommand()); + getCommand("bindonpickup").setExecutor(new BindOnPickupCommand()); + getCommand("bindonuse").setExecutor(new BindOnUseCommand()); + getCommand("bindonequip").setExecutor(new BindOnEquipCommand()); + getCommand("unbind").setExecutor(new UnbindCommand()); + + checkForUpdates(); + } + + private void registerEvents() { + PluginManager pm = getServer().getPluginManager(); + pm.registerEvents(new PlayerListener(), this); + pm.registerEvents(new InventoryListener(), this); + pm.registerEvents(new EntityListener(), this); + pm.registerEvents(new BlockListener(), this); + } + + private void setupEpicBossRecoded() { + if (getServer().getPluginManager().isPluginEnabled("EpicBossRecoded")) { + epicBossRecodedEnabled = true; + debug("EpicBossRecoded found!"); + getServer().getPluginManager().registerEvents(new EpicBossRecodedListener(), this); + } + } + + /** + * Run things on disable. + */ + @Override + public void onDisable() {} + + public static String getMainDirectory() { + return mainDirectory; + } + + public boolean isUpdateAvailable() { + return updateAvailable; + } + + public void debug(String message) { + getLogger().info("[Debug] " + message); + } + + /** + * Setup the various storage file paths + */ + private void setupFilePaths() { + soulbound = getFile(); + mainDirectory = getDataFolder().getPath() + File.separator; + } + + private void loadConfigFiles() { + Config.getInstance(); + ItemsConfig.getInstance(); + } + + private void checkForUpdates() { + if (!Config.getInstance().getUpdateCheckEnabled()) { + return; + } + + Updater updater = new Updater(this, 53483, soulbound, Updater.UpdateType.NO_DOWNLOAD, false); + + if (updater.getResult() != Updater.UpdateResult.UPDATE_AVAILABLE) { + this.updateAvailable = false; + return; + } + + if (updater.getLatestType().equals("beta") && !Config.getInstance().getPreferBeta()) { + this.updateAvailable = false; + return; + } + + this.updateAvailable = true; + getLogger().info("Soulbound is outdated!"); + getLogger().info("http://dev.bukkit.org/server-mods/soulbound/"); + } +} diff --git a/src/main/java/com/me/tft_02/soulbound/api/ItemAPI.java b/src/main/java/com/me/tft_02/soulbound/api/ItemAPI.java new file mode 100644 index 0000000..ae870c5 --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/api/ItemAPI.java @@ -0,0 +1,116 @@ +package com.me.tft_02.soulbound.api; + +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import com.me.tft_02.soulbound.util.ItemUtils; + +public final class ItemAPI { + private ItemAPI() {} + + /** + * Check if a Player is binded to an ItemStack. + *
+ * This function is designed for API usage. + * + * @param player The Player to check + * @param itemStack The ItemStack to check + * + * @return true or false + */ + public static boolean isSoulbindedPlayer(Player player, ItemStack itemStack) { + return ItemUtils.isBindedPlayer(player, itemStack); + } + + /** + * Soulbind an ItemStack to a Player. + *
+ * This function is designed for API usage. + * + * @param player The Player to bind the item to + * @param itemStack The ItemStack to bind + * + * @return the soulbound ItemStack + */ + public static ItemStack soulbindItem(Player player, ItemStack itemStack) { + return ItemUtils.soulbindItem(player, itemStack); + } + + /** + * Check if an itemstack is Soulbound. + *
+ * This function is designed for API usage. + * + * @param itemStack The itemstack to check + * + * @return true or false + */ + public static boolean isSoulbound(ItemStack itemStack) { + return ItemUtils.isSoulbound(itemStack); + } + + /** + * Mark an itemstack as Bind on Pickup + *
+ * This function is designed for API usage. + * + * @param itemStack The itemstack to mark as Bind on Pickup + * + * @return the marked itemstack + */ + public static ItemStack bindOnPickupItem(ItemStack itemStack) { + return ItemUtils.bopItem(itemStack); + } + + /** + * Check if an itemstack is Bind on Pickup. + *
+ * This function is designed for API usage. + * + * @param itemStack The itemstack to check + * + * @return true or false + */ + public static boolean isBindOnPickup(ItemStack itemStack) { + return ItemUtils.isBindOnPickup(itemStack); + } + + /** + * Mark an itemstack as Bind on Use + *
+ * This function is designed for API usage. + * + * @param itemStack The itemstack to mark as Bind on Use + * + * @return the marked itemstack + */ + public static ItemStack bindOnUseItem(ItemStack itemStack) { + return ItemUtils.bouItem(itemStack); + } + + /** + * Mark an itemstack as Bind on Equip + *
+ * This function is designed for API usage. + * + * @param itemStack The itemstack to mark as Bind on Equip + * + * @return the marked itemstack + */ + public static ItemStack bindOnEquipItem(ItemStack itemStack) { + return ItemUtils.boeItem(itemStack); + } + + /** + * Get the Soulbound type of an itemstack. + *
+ * This function is designed for API usage. + * + * @param itemStack The itemstack to check + * + * @return the Bind type + */ + public static String getItemType(ItemStack itemStack) { + return ItemUtils.getItemType(itemStack).toString(); + } +} diff --git a/src/main/java/com/me/tft_02/soulbound/commands/BindCommand.java b/src/main/java/com/me/tft_02/soulbound/commands/BindCommand.java new file mode 100644 index 0000000..9a4caf5 --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/commands/BindCommand.java @@ -0,0 +1,92 @@ +package com.me.tft_02.soulbound.commands; + +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import com.me.tft_02.soulbound.Soulbound; +import com.me.tft_02.soulbound.config.Config; +import com.me.tft_02.soulbound.util.CommandUtils; +import com.me.tft_02.soulbound.util.ItemUtils; + +public class BindCommand implements CommandExecutor { + String soulbound = ChatColor.GOLD + "Soulbound "; + + @SuppressWarnings("deprecation") + @Override + public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { + if (CommandUtils.noConsoleUsage(sender)) { + return true; + } + + if (!sender.hasPermission("soulbound.commands.bind")) { + return false; + } + + boolean bindFullInventory = false; + + Player player = (Player) sender; + Player target; + switch (args.length) { + case 1: + target = Soulbound.p.getServer().getPlayerExact(args[0]); + + if (CommandUtils.isOffline(sender, target)) { + return true; + } + + break; + case 2: + if (!args[1].equalsIgnoreCase("inventory")) { + sender.sendMessage(ChatColor.RED + "Proper usage: " + ChatColor.GREEN + "/bind inventory"); + return true; + } + + bindFullInventory = true; + target = Soulbound.p.getServer().getPlayerExact(args[0]); + + if (CommandUtils.isOffline(sender, target)) { + return true; + } + + break; + default: + target = player; + } + + if (bindFullInventory) { + return handleBindFullInventory(player, target); + } + + ItemStack itemInHand = player.getItemInHand(); + + if ((itemInHand.getType() == Material.AIR) || ItemUtils.isSoulbound(itemInHand)) { + sender.sendMessage(ChatColor.GRAY + "You can't " + soulbound + ChatColor.GRAY + "this item."); + return false; + } + + ItemUtils.soulbindItem(target, itemInHand); + + if (ItemUtils.isSoulbound(itemInHand) && Config.getInstance().getFeedbackEnabled()) { + sender.sendMessage(ChatColor.GRAY + "Item is now " + soulbound + ChatColor.GRAY + "to " + ChatColor.DARK_AQUA + target.getName()); + } + return true; + } + + private boolean handleBindFullInventory(Player player, Player target) { + for (ItemStack itemStack : player.getInventory().getContents()) { + if (itemStack != null && itemStack.getType() != Material.AIR) { + ItemUtils.soulbindItem(target, itemStack); + } + } + + if (Config.getInstance().getFeedbackEnabled()) { + player.sendMessage(ChatColor.GRAY + "All items in " + ChatColor.DARK_AQUA + target.getName() + ChatColor.GRAY + "'s inventory are now " + soulbound + ChatColor.GRAY + "to " + ChatColor.DARK_AQUA + target.getName()); + } + return true; + } +} diff --git a/src/main/java/com/me/tft_02/soulbound/commands/BindOnEquipCommand.java b/src/main/java/com/me/tft_02/soulbound/commands/BindOnEquipCommand.java new file mode 100644 index 0000000..10051f7 --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/commands/BindOnEquipCommand.java @@ -0,0 +1,51 @@ +package com.me.tft_02.soulbound.commands; + +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import com.me.tft_02.soulbound.util.CommandUtils; +import com.me.tft_02.soulbound.util.ItemUtils; + +public class BindOnEquipCommand implements CommandExecutor { + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (CommandUtils.noConsoleUsage(sender)) { + return true; + } + + switch (args.length) { + case 0: + Player player = (Player) sender; + + if (!player.hasPermission("soulbound.commands.bindonequip")) { + return false; + } + + ItemStack itemInHand = player.getItemInHand(); + + if ((itemInHand.getType() == Material.AIR) || ItemUtils.isSoulbound(itemInHand)) { + player.sendMessage(ChatColor.GRAY + "You can't " + ChatColor.GOLD + "Soulbound " + ChatColor.GRAY + "this item."); + return false; + } + + ItemUtils.unbindItem(itemInHand); + ItemUtils.boeItem(itemInHand); + + if (ItemUtils.isBindOnEquip(itemInHand)) { + player.sendMessage(ChatColor.GRAY + "Item is now " + ChatColor.DARK_RED + "Bind on Equip"); + } + else { + player.sendMessage(ChatColor.RED + "Cannot mark this item as " + ChatColor.DARK_RED + "Bind on Equip"); + } + return true; + + default: + return false; + } + } +} diff --git a/src/main/java/com/me/tft_02/soulbound/commands/BindOnPickupCommand.java b/src/main/java/com/me/tft_02/soulbound/commands/BindOnPickupCommand.java new file mode 100644 index 0000000..91ccae0 --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/commands/BindOnPickupCommand.java @@ -0,0 +1,45 @@ +package com.me.tft_02.soulbound.commands; + +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import com.me.tft_02.soulbound.util.CommandUtils; +import com.me.tft_02.soulbound.util.ItemUtils; + +public class BindOnPickupCommand implements CommandExecutor { + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (CommandUtils.noConsoleUsage(sender)) { + return true; + } + + switch (args.length) { + case 0: + Player player = (Player) sender; + + if (!player.hasPermission("soulbound.commands.bindonpickup")) { + return false; + } + + ItemStack itemInHand = player.getItemInHand(); + + if ((itemInHand.getType() == Material.AIR) || ItemUtils.isSoulbound(itemInHand)) { + player.sendMessage(ChatColor.GRAY + "You can't " + ChatColor.GOLD + "Soulbound " + ChatColor.GRAY + "this item."); + return false; + } + + ItemUtils.unbindItem(itemInHand); + ItemUtils.bopItem(itemInHand); + player.sendMessage(ChatColor.GRAY + "Item is now " + ChatColor.DARK_RED + "Bind on pickup"); + + return true; + default: + return false; + } + } +} diff --git a/src/main/java/com/me/tft_02/soulbound/commands/BindOnUseCommand.java b/src/main/java/com/me/tft_02/soulbound/commands/BindOnUseCommand.java new file mode 100644 index 0000000..ba87085 --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/commands/BindOnUseCommand.java @@ -0,0 +1,45 @@ +package com.me.tft_02.soulbound.commands; + +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import com.me.tft_02.soulbound.util.CommandUtils; +import com.me.tft_02.soulbound.util.ItemUtils; + +public class BindOnUseCommand implements CommandExecutor { + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (CommandUtils.noConsoleUsage(sender)) { + return true; + } + + switch (args.length) { + case 0: + Player player = (Player) sender; + + if (!player.hasPermission("soulbound.commands.bindonuse")) { + return false; + } + + ItemStack itemInHand = player.getItemInHand(); + + if ((itemInHand.getType() == Material.AIR) || ItemUtils.isSoulbound(itemInHand)) { + player.sendMessage(ChatColor.GRAY + "You can't " + ChatColor.GOLD + "Soulbound " + ChatColor.GRAY + "this item."); + return false; + } + + ItemUtils.unbindItem(itemInHand); + ItemUtils.bouItem(itemInHand); + player.sendMessage(ChatColor.GRAY + "Item is now " + ChatColor.DARK_RED + "Bind on Use"); + + return true; + default: + return false; + } + } +} diff --git a/src/main/java/com/me/tft_02/soulbound/commands/SoulboundCommand.java b/src/main/java/com/me/tft_02/soulbound/commands/SoulboundCommand.java new file mode 100644 index 0000000..23e1370 --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/commands/SoulboundCommand.java @@ -0,0 +1,134 @@ +package com.me.tft_02.soulbound.commands; + +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import com.me.tft_02.soulbound.Soulbound; + +public class SoulboundCommand implements CommandExecutor { + @Override + public boolean onCommand(CommandSender sender, Command command, + String label, String[] args) { + switch (args.length) { + case 0: + sender.sendMessage("Soulbound version " + + Soulbound.p.getDescription().getVersion()); + return printUsage(sender); + case 1: + if (args[0].equalsIgnoreCase("reload")) { + return reloadConfiguration(sender); + } + default: + if (args[0].equalsIgnoreCase("help") + || args[0].equalsIgnoreCase("?")) { + return helpPages(sender, args); + } + return false; + } + } + + private boolean reloadConfiguration(CommandSender sender) { + if (sender instanceof Player + && !sender.hasPermission("soulbound.commands.reload")) { + return false; + } + + Soulbound.p.reloadConfig(); + sender.sendMessage(ChatColor.GREEN + "Configuration reloaded."); + return false; + } + + private boolean helpPages(CommandSender sender, String[] args) { + if (!(sender instanceof Player)) { + sender.sendMessage("Can't use this from the console, sorry!"); + return false; + } + if (args.length >= 2 && Integer.parseInt(args[1]) > 0) { + getHelpPage(Integer.parseInt(args[1]), sender); + return true; + } + return false; + } + + private void getHelpPage(int page, CommandSender sender) { + int maxPages = 2; + int nextPage = page + 1; + if (page > maxPages) { + sender.sendMessage(ChatColor.RED + "This page does not exist." + + ChatColor.GOLD + " /help [0-" + maxPages + "]"); + return; + } + + String dot = ChatColor.DARK_RED + "* "; + sender.sendMessage(ChatColor.GRAY + "-----[ " + ChatColor.GOLD + + "Soulbound Help" + ChatColor.GRAY + " ]----- Page " + page + + "/" + maxPages); + if (page == 1) { + sender.sendMessage(ChatColor.GOLD + "How does it work?"); + sender.sendMessage(dot + + ChatColor.GRAY + + "Soulbound items are special items which are bound to a sender."); + sender.sendMessage(dot + + ChatColor.GRAY + + "Players are prevented from doing certain actions with Soulbound items, such as:"); + sender.sendMessage(dot + + ChatColor.GRAY + + "dropping them on the ground, storing them in chests or giving them to other players."); + sender.sendMessage(dot + + ChatColor.GRAY + + "Items marked as 'Bind on Pickup' will get Soulbound as soon as they get picked up."); + sender.sendMessage(dot + + ChatColor.GRAY + + "Items marked as 'Bind on Use' will get Soulbound as soon as they get used."); + sender.sendMessage(dot + + ChatColor.GRAY + + "Items marked as 'Bind on Equip' will get Soulbound as soon as they get equipped."); + } + if (page == 2) { + sender.sendMessage(ChatColor.GOLD + "Commands:"); + if (sender.hasPermission("soulbound.commands.bindonpickup")) { + sender.sendMessage(dot + ChatColor.GREEN + "/soulbound" + + ChatColor.GRAY + " Check the status of the plugin."); + } + if (sender.hasPermission("soulbound.commands.bind")) { + sender.sendMessage(dot + ChatColor.GREEN + "/bind " + + ChatColor.GRAY + + " Soulbound the item currently in hand."); + sender.sendMessage(dot + ChatColor.GREEN + + "/bind inventory" + ChatColor.GRAY + + " Soulbound an entire inventory."); + } + if (sender.hasPermission("soulbound.commands.bindonpickup")) { + sender.sendMessage(dot + ChatColor.GREEN + "/bindonpickup" + + ChatColor.GRAY + + " Mark the item in hand as 'Bind on Pickup'"); + } + if (sender.hasPermission("soulbound.commands.bindonuse")) { + sender.sendMessage(dot + ChatColor.GREEN + "/bindonuse" + + ChatColor.GRAY + + " Mark the item in hand as 'Bind on Use'"); + } + if (sender.hasPermission("soulbound.commands.bindonequip")) { + sender.sendMessage(dot + ChatColor.GREEN + "/bindonequip" + + ChatColor.GRAY + + " Mark the item in hand as 'Bind on Equip'"); + } + if (sender.hasPermission("soulbound.commands.unbind")) { + sender.sendMessage(dot + ChatColor.GREEN + "/unbind" + + ChatColor.GRAY + " Unbind the item in hand."); + } + } + if (nextPage <= maxPages) { + sender.sendMessage(ChatColor.GOLD + "Type /soulbound help " + + nextPage + " for more"); + } + } + + private boolean printUsage(CommandSender sender) { + sender.sendMessage("Usage: /soulbound [reload | help]"); + return false; + } +} diff --git a/src/main/java/com/me/tft_02/soulbound/commands/UnbindCommand.java b/src/main/java/com/me/tft_02/soulbound/commands/UnbindCommand.java new file mode 100644 index 0000000..be10e2c --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/commands/UnbindCommand.java @@ -0,0 +1,47 @@ +package com.me.tft_02.soulbound.commands; + +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import com.me.tft_02.soulbound.util.CommandUtils; +import com.me.tft_02.soulbound.util.ItemUtils; + +public class UnbindCommand implements CommandExecutor { + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (CommandUtils.noConsoleUsage(sender)) { + return true; + } + + switch (args.length) { + case 0: + Player player = (Player) sender; + + if (!player.hasPermission("soulbound.commands.unbind")) { + return false; + } + + ItemStack itemInHand = player.getItemInHand(); + + if ((itemInHand.getType() == Material.AIR) || !ItemUtils.isSoulbound(itemInHand)) { + player.sendMessage(ChatColor.GRAY + "You can't " + ChatColor.GOLD + "Unbind " + ChatColor.GRAY + "this item."); + return false; + } + + if (ItemUtils.unbindItem(itemInHand)==null ){ + player.sendMessage(ChatColor.GRAY + "You can't " + ChatColor.GOLD + "Unbind " + ChatColor.GRAY + "this item."); + return false; + } + + player.sendMessage(ChatColor.GRAY + "Item no longer Soulbound."); + return true; + default: + return false; + } + } +} diff --git a/src/main/java/com/me/tft_02/soulbound/config/AutoUpdateConfigLoader.java b/src/main/java/com/me/tft_02/soulbound/config/AutoUpdateConfigLoader.java new file mode 100644 index 0000000..0774904 --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/config/AutoUpdateConfigLoader.java @@ -0,0 +1,119 @@ +package com.me.tft_02.soulbound.config; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.InputStreamReader; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; + +public abstract class AutoUpdateConfigLoader extends ConfigLoader { + public AutoUpdateConfigLoader(String relativePath, String fileName) { + super(relativePath, fileName); + } + + public AutoUpdateConfigLoader(String fileName) { + super(fileName); + } + + @SuppressWarnings("deprecation") + @Override + protected void loadFile() { + super.loadFile(); + FileConfiguration internalConfig = YamlConfiguration.loadConfiguration(plugin.getResource(fileName)); + + Set configKeys = config.getKeys(true); + Set internalConfigKeys = internalConfig.getKeys(true); + + boolean needSave = false; + + Set oldKeys = new HashSet(configKeys); + oldKeys.removeAll(internalConfigKeys); + + Set newKeys = new HashSet(internalConfigKeys); + newKeys.removeAll(configKeys); + + // Don't need a re-save if we have old keys sticking around? + // Would be less saving, but less... correct? + if (!newKeys.isEmpty() || !oldKeys.isEmpty()) { + needSave = true; + } + + for (String key : oldKeys) { + plugin.debug("Removing unused key: " + key); + config.set(key, null); + } + + for (String key : newKeys) { + plugin.debug("Adding new key: " + key + " = " + internalConfig.get(key)); + config.set(key, internalConfig.get(key)); + } + + if (needSave) { + // Get Bukkit's version of an acceptable config with new keys, and no old keys + String output = config.saveToString(); + + // Convert to the superior 4 space indentation + output = output.replace(" ", " "); + + // Rip out Bukkit's attempt to save comments at the top of the file + while (output.indexOf('#') != -1) { + output = output.substring(output.indexOf('\n', output.indexOf('#')) + 1); + } + + // Read the internal config to get comments, then put them in the new one + try { + // Read internal + BufferedReader reader = new BufferedReader(new InputStreamReader(plugin.getResource(fileName))); + HashMap comments = new HashMap(); + String temp = ""; + + String line; + while ((line = reader.readLine()) != null) { + if (line.contains("#")) { + temp += line + "\n"; + } + else if (line.contains(":")) { + line = line.substring(0, line.indexOf(":") + 1); + if (!temp.isEmpty()) { + comments.put(line, temp); + temp = ""; + } + } + } + + // Dump to the new one + for (String key : comments.keySet()) { + if (output.indexOf(key) != -1) { + output = output.substring(0, output.indexOf(key)) + comments.get(key) + output.substring(output.indexOf(key)); + } + } + } + catch (Exception e) { + e.printStackTrace(); + } + + // Save it + try { + String saveName = fileName; + // At this stage we cannot guarantee that Config has been loaded, so we do the check directly here + if (!plugin.getConfig().getBoolean("General.Config_Update_Overwrite", true)) { + saveName += ".new"; + } + + BufferedWriter writer = new BufferedWriter(new FileWriter(new File(plugin.getDataFolder(), saveName))); + writer.write(output); + writer.flush(); + writer.close(); + } + catch (Exception e) { + e.printStackTrace(); + } + } + } +} diff --git a/src/main/java/com/me/tft_02/soulbound/config/Config.java b/src/main/java/com/me/tft_02/soulbound/config/Config.java new file mode 100644 index 0000000..89700f2 --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/config/Config.java @@ -0,0 +1,89 @@ +package com.me.tft_02.soulbound.config; + +import java.util.ArrayList; +import java.util.List; + +public class Config extends AutoUpdateConfigLoader { + private static Config instance; + + private Config() { + super("config.yml"); + } + + public static Config getInstance() { + if (instance == null) { + instance = new Config(); + } + + return instance; + } + + @Override + protected void loadKeys() {} + + /* @formatter:off */ + + /* GENERAL SETTINGS */ +// public String getLocale() { return config.getString("General.Locale", "en_us"); } +// public int getSaveInterval() { return config.getInt("General.Save_Interval", 10); } + public boolean getStatsTrackingEnabled() { return config.getBoolean("General.Stats_Tracking", true); } + public boolean getUpdateCheckEnabled() { return config.getBoolean("General.Update_Check", true); } + public boolean getPreferBeta() { return config.getBoolean("General.Prefer_Beta", false); } + public boolean getVerboseLoggingEnabled() { return config.getBoolean("General.Verbose_Logging", false); } + public boolean getConfigOverwriteEnabled() { return config.getBoolean("General.Config_Update_Overwrite", true); } + + /* @formatter:on */ + + /* SOULBOUND SETTINGS */ + public boolean getShowNameInLore() { return config.getBoolean("Soulbound.Show_Name_In_Lore", true); } + public boolean getFeedbackEnabled() { return config.getBoolean("Soulbound.Feedback_Messages_Enabled", true); } + public boolean getPreventItemDrop() { return config.getBoolean("Soulbound.Prevent_Item_Drop", false); } + public boolean getDeleteOnDrop() { return config.getBoolean("Soulbound.Delete_On_Drop", false); } + public boolean getAllowItemStoring() { return config.getBoolean("Soulbound.Allow_Item_Storing", true); } + public boolean getInfiniteDurability() { return config.getBoolean("Soulbound.Infinite_Durability", false); } + + public List getBlockedCommands() { return config.getStringList("Soulbound.Blocked_Commands"); } + public List getBindCommands() { return config.getStringList("Soulbound.Commands_Bind_When_Used"); } + + // EpicBossRecoded config settings + + public boolean getEBRBindOnPickup() { return config.getBoolean("Dependency_Plugins.EpicBossRecoded.BindOnPickup"); } + public boolean getEBRBindOnEquip() { return config.getBoolean("Dependency_Plugins.EpicBossRecoded.BindOnEquip"); } + public boolean getEBRBindOnUse() { return config.getBoolean("Dependency_Plugins.EpicBossRecoded.BindOnUse");} + + // DiabloDrops config settings + + public List getDiabloDropsBindOnPickupTiers() { return getDiabloDropsItemTiers("BindOnPickup");} + public List getDiabloDropsBindOnUseTiers() { return getDiabloDropsItemTiers("BindOnUse");} + public List getDiabloDropsBindOnEquipTiers() { return getDiabloDropsItemTiers("BindOnEquip"); } + + public List getDiabloDropsItemTiers(String bindType) { + String[] tiersString = config.getString("Dependency_Plugins.DiabloDrops." + bindType).replaceAll(" ", "").split("[,]"); + List tiers = new ArrayList(); + + for (String tier : tiersString) { + tiers.add(tier); + } + return tiers; + } + + // LoreLocks config settings + + public boolean getLoreLocksBindKeys() { return config.getBoolean("Dependency_Plugins.LoreLocks.Bind_Keys"); } + + // MythicDrops config settings + + public List getMythicDropsBindOnPickupTiers() { return getMythicDropsItemTiers("BindOnPickup"); } + public List getMythicDropsBindOnUseTiers() { return getMythicDropsItemTiers("BindOnUse"); } + public List getMythicDropsBindOnEquipTiers() { return getMythicDropsItemTiers("BindOnEquip"); } + + public List getMythicDropsItemTiers(String bindType) { + String[] tiersString = config.getString("Dependency_Plugins.MythicDrops." + bindType).replaceAll(" ", "").split("[,]"); + List tiers = new ArrayList(); + + for (String tier : tiersString) { + tiers.add(tier); + } + return tiers; + } +} diff --git a/src/main/java/com/me/tft_02/soulbound/config/ConfigLoader.java b/src/main/java/com/me/tft_02/soulbound/config/ConfigLoader.java new file mode 100644 index 0000000..03b24dc --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/config/ConfigLoader.java @@ -0,0 +1,93 @@ +package com.me.tft_02.soulbound.config; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; + +import com.me.tft_02.soulbound.Soulbound; + +public abstract class ConfigLoader { + protected static final Soulbound plugin = Soulbound.p; + protected String fileName; + protected File configFile; + protected FileConfiguration config; + + public ConfigLoader(String relativePath, String fileName) { + this.fileName = fileName; + configFile = new File(plugin.getDataFolder(), relativePath + File.separator + fileName); + loadFile(); + } + + public ConfigLoader(String fileName) { + this.fileName = fileName; + configFile = new File(plugin.getDataFolder(), fileName); + loadFile(); + } + + protected void loadFile() { + if (!configFile.exists()) { + plugin.debug("Creating Soulbound " + fileName + " File..."); + createFile(); + } + else { + plugin.debug("Loading Soulbound " + fileName + " File..."); + } + + config = YamlConfiguration.loadConfiguration(configFile); + } + + protected abstract void loadKeys(); + + protected void createFile() { + configFile.getParentFile().mkdirs(); + + InputStream inputStream = plugin.getResource(fileName); + + if (inputStream == null) { + plugin.getLogger().severe("Missing resource file: '" + fileName + "' please notify the plugin authors"); + return; + } + + OutputStream outputStream = null; + + try { + outputStream = new FileOutputStream(configFile); + + int read; + byte[] bytes = new byte[1024]; + + while ((read = inputStream.read(bytes)) != -1) { + outputStream.write(bytes, 0, read); + } + } + catch (FileNotFoundException e) { + e.printStackTrace(); + } + catch (IOException e) { + e.printStackTrace(); + } + finally { + if (outputStream != null) { + try { + outputStream.close(); + } + catch (IOException e) { + e.printStackTrace(); + } + } + + try { + inputStream.close(); + } + catch (IOException e) { + e.printStackTrace(); + } + } + } +} diff --git a/src/main/java/com/me/tft_02/soulbound/config/ItemsConfig.java b/src/main/java/com/me/tft_02/soulbound/config/ItemsConfig.java new file mode 100644 index 0000000..23732c5 --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/config/ItemsConfig.java @@ -0,0 +1,155 @@ +package com.me.tft_02.soulbound.config; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.material.MaterialData; + +import com.me.tft_02.soulbound.datatypes.ActionType; +import com.me.tft_02.soulbound.datatypes.SoulbindItem; + +public class ItemsConfig extends ConfigLoader { + private static ItemsConfig instance; + + private List soulbindOnCraft = new ArrayList(); + private List soulbindOnOpenChest = new ArrayList(); + private List soulbindOnPickupItem = new ArrayList(); + private List soulbindOnDrop = new ArrayList(); + private List soulbindOnRespawn = new ArrayList(); + private List soulbindOnKit = new ArrayList(); + + public ItemsConfig() { + super("items.yml"); + loadKeys(); + } + + public static ItemsConfig getInstance() { + if (instance == null) { + instance = new ItemsConfig(); + } + + return instance; + } + + @SuppressWarnings("deprecation") + @Override + protected void loadKeys() { + ConfigurationSection configurationSection = config.getConfigurationSection("Items"); + + if (configurationSection == null) { + return; + } + + Set itemConfigSet = configurationSection.getKeys(false); + + for (String itemName : itemConfigSet) { + String[] itemInfo = itemName.split("[|]"); + + Material itemMaterial = Material.matchMaterial(itemInfo[0]); + + if (itemMaterial == null) { + plugin.getLogger().warning("Invalid material name. This item will be skipped. - " + itemInfo[0]); + continue; + } + + byte itemData = (itemInfo.length == 2) ? Byte.valueOf(itemInfo[1]) : 0; + MaterialData itemMaterialData = new MaterialData(itemMaterial, itemData); + + List lore = new ArrayList(); + if (config.contains("Items." + itemName + ".Lore")) { + + for (String loreEntry : config.getStringList("Items." + itemName + ".Lore")) { + lore.add(ChatColor.translateAlternateColorCodes('&', loreEntry)); + } + } + + String name = null; + if (config.contains("Items." + itemName + ".Name")) { + name = ChatColor.translateAlternateColorCodes('&', config.getString("Items." + itemName + ".Name")); + } + + SoulbindItem soulbindItem = new SoulbindItem(itemMaterialData, name, lore); + + List actions = config.getStringList("Items." + itemName + ".Actions"); + + for (ActionType actionType : ActionType.values()) { + if (actions.contains(actionType.toString())) { + addSoulbindItem(actionType, soulbindItem); + } + } + } + } + + private void addSoulbindItem(ActionType actionType, SoulbindItem soulbindItem) { + switch (actionType) { + case CRAFT: + soulbindOnCraft.add(soulbindItem); + return; + case OPEN_CHEST: + soulbindOnOpenChest.add(soulbindItem); + return; + case PICKUP_ITEM: + soulbindOnPickupItem.add(soulbindItem); + return; + case DROP_ITEM: + soulbindOnDrop.add(soulbindItem); + return; + case RESPAWN: + soulbindOnRespawn.add(soulbindItem); + return; + case KIT: + soulbindOnKit.add(soulbindItem); + return; + } + } + + public List getSoulbindItems(ActionType actionType) { + switch (actionType) { + case CRAFT: + return soulbindOnCraft; + case OPEN_CHEST: + return soulbindOnOpenChest; + case PICKUP_ITEM: + return soulbindOnPickupItem; + case DROP_ITEM: + return soulbindOnDrop; + case RESPAWN: + return soulbindOnRespawn; + case KIT: + return soulbindOnKit; + default: + return null; + } + } + + public boolean isActionItem(ItemStack itemStack, ActionType actionType) { + for (SoulbindItem soulbindItem : getSoulbindItems(actionType)) { + if (itemStack.getData().equals(soulbindItem.getMaterialData())) { + if (itemStack.hasItemMeta()) { + ItemMeta itemMeta = itemStack.getItemMeta(); + + if (soulbindItem.getName() != null) { + if (!itemMeta.getDisplayName().contains(soulbindItem.getName())) { + return false; + } + } + + if (soulbindItem.getLore() != null && !soulbindItem.getLore().isEmpty()) { + if (itemMeta.hasLore() || !itemMeta.getLore().containsAll(soulbindItem.getLore())) { + return false; + } + } + } + return true; + } + } + + return false; + } +} diff --git a/src/main/java/com/me/tft_02/soulbound/datatypes/ActionType.java b/src/main/java/com/me/tft_02/soulbound/datatypes/ActionType.java new file mode 100644 index 0000000..f6451af --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/datatypes/ActionType.java @@ -0,0 +1,10 @@ +package com.me.tft_02.soulbound.datatypes; + +public enum ActionType { + CRAFT, + OPEN_CHEST, + PICKUP_ITEM, + DROP_ITEM, + RESPAWN, + KIT; +}; diff --git a/src/main/java/com/me/tft_02/soulbound/datatypes/SoulbindItem.java b/src/main/java/com/me/tft_02/soulbound/datatypes/SoulbindItem.java new file mode 100644 index 0000000..b1d4571 --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/datatypes/SoulbindItem.java @@ -0,0 +1,34 @@ +package com.me.tft_02.soulbound.datatypes; + +import java.util.List; + +import org.bukkit.material.MaterialData; + +public class SoulbindItem { + private MaterialData materialData; + private String name; + private List lore; + + public SoulbindItem(MaterialData materialData, String name, List lore) { + this.materialData = materialData; + this.name = name; + this.lore = lore; + } + + public MaterialData getMaterialData() { + return materialData; + } + + public List getLore() { + return lore; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return materialData.toString() + " " + name.toString() + " " + lore.toString(); + } +} diff --git a/src/main/java/com/me/tft_02/soulbound/events/SoulbindItemEvent.java b/src/main/java/com/me/tft_02/soulbound/events/SoulbindItemEvent.java new file mode 100644 index 0000000..a3a5d5a --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/events/SoulbindItemEvent.java @@ -0,0 +1,54 @@ +package com.me.tft_02.soulbound.events; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.ItemStack; + +public class SoulbindItemEvent extends PlayerEvent implements Cancellable { + private ItemStack itemStack; + private boolean cancelled; + + public SoulbindItemEvent(Player player, ItemStack itemStack) { + super(player); + + this.setItemStack(itemStack); + this.cancelled = false; + } + + /** + * @return The itemStack being soulbound + */ + public ItemStack getItemStack() { + return itemStack; + } + + /** + * @return Set the itemStack being soulbound + */ + public void setItemStack(ItemStack itemStack) { + this.itemStack = itemStack; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + private static final HandlerList handlers = new HandlerList(); + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/com/me/tft_02/soulbound/hooks/EpicBossRecodedListener.java b/src/main/java/com/me/tft_02/soulbound/hooks/EpicBossRecodedListener.java new file mode 100644 index 0000000..1d31778 --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/hooks/EpicBossRecodedListener.java @@ -0,0 +1,40 @@ +package com.me.tft_02.soulbound.hooks; + +//import me.ThaH3lper.com.Api.BossDeathEvent; + +import org.bukkit.event.Listener; +import org.bukkit.inventory.ItemStack; + +import com.me.tft_02.soulbound.config.Config; +import com.me.tft_02.soulbound.util.ItemUtils; + +public class EpicBossRecodedListener implements Listener { + + /** + * Check BossDeathEvent events. + * + * @param event The event to check + */ +/* @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onBossDeath(BossDeathEvent event) { + if (event.getDrops().isEmpty()) { + return; + } + + for (ItemStack itemStack : event.getDrops()) { + handleEpicBossItems(itemStack); + } + } +*/ + public void handleEpicBossItems(ItemStack itemStack) { + if (Config.getInstance().getEBRBindOnEquip() && ItemUtils.isEquipable(itemStack)) { + ItemUtils.boeItem(itemStack); + } + else if (Config.getInstance().getEBRBindOnPickup()) { + ItemUtils.bopItem(itemStack); + } + else if (Config.getInstance().getEBRBindOnUse()) { + ItemUtils.bouItem(itemStack); + } + } +} diff --git a/src/main/java/com/me/tft_02/soulbound/listeners/BlockListener.java b/src/main/java/com/me/tft_02/soulbound/listeners/BlockListener.java new file mode 100644 index 0000000..38f58a2 --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/listeners/BlockListener.java @@ -0,0 +1,19 @@ +package com.me.tft_02.soulbound.listeners; + +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.inventory.ItemStack; + +import com.me.tft_02.soulbound.util.DurabilityUtils; + +public class BlockListener implements Listener { + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onBlockBreak(BlockBreakEvent event) { + ItemStack itemStack = event.getPlayer().getItemInHand(); + + DurabilityUtils.handleInfiniteDurability(itemStack); + } +} diff --git a/src/main/java/com/me/tft_02/soulbound/listeners/EntityListener.java b/src/main/java/com/me/tft_02/soulbound/listeners/EntityListener.java new file mode 100644 index 0000000..e7cdcd3 --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/listeners/EntityListener.java @@ -0,0 +1,80 @@ +package com.me.tft_02.soulbound.listeners; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityShootBowEvent; +import org.bukkit.inventory.ItemStack; + +import com.me.tft_02.soulbound.util.DurabilityUtils; + +public class EntityListener implements Listener { + + /** + * Check EntityDamageByEntityEvent events. + * + * @param event The event to check + */ + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onEntityDeath(EntityDamageByEntityEvent event) { + if (event.getDamage() == 0 || event.getEntity().isDead()) { + return; + } + + if (event.getEntity() instanceof LivingEntity) { + combatChecks(event); + } + } + + /** + * Apply combat modifiers + * + * @param event The event to run the combat checks on. + */ + void combatChecks(EntityDamageByEntityEvent event) { + Entity damager = event.getDamager(); + EntityType damagerType = damager.getType(); + + switch (damagerType) { + case PLAYER: + Player attacker = (Player) event.getDamager(); + ItemStack itemInHand = attacker.getItemInHand(); + + DurabilityUtils.handleInfiniteDurability(itemInHand); + default: + return; + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + private void onEntityShootBow(EntityShootBowEvent event) { + Entity entity = event.getEntity(); + + if (entity instanceof Player) { + DurabilityUtils.handleInfiniteDurability(((Player) entity).getItemInHand()); + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + private void onEntityDamage(EntityDamageEvent event) { + if (event.getDamage() == 0 || event.getEntity().isDead()) { + return; + } + + Entity entity = event.getEntity(); + + if (entity instanceof Player) { + Player player = (Player) entity; + + for (ItemStack itemStack : player.getInventory().getArmorContents()) { + DurabilityUtils.handleInfiniteDurability(itemStack); + } + } + } +} diff --git a/src/main/java/com/me/tft_02/soulbound/listeners/InventoryListener.java b/src/main/java/com/me/tft_02/soulbound/listeners/InventoryListener.java new file mode 100644 index 0000000..b886a5b --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/listeners/InventoryListener.java @@ -0,0 +1,179 @@ +package com.me.tft_02.soulbound.listeners; + +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.enchantment.EnchantItemEvent; +import org.bukkit.event.inventory.CraftItemEvent; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryMoveItemEvent; +import org.bukkit.event.inventory.InventoryOpenEvent; +import org.bukkit.event.inventory.InventoryPickupItemEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.event.inventory.InventoryType.SlotType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import com.me.tft_02.soulbound.Soulbound; +import com.me.tft_02.soulbound.config.Config; +import com.me.tft_02.soulbound.config.ItemsConfig; +import com.me.tft_02.soulbound.datatypes.ActionType; +import com.me.tft_02.soulbound.runnables.UpdateArmorTask; +import com.me.tft_02.soulbound.util.ItemUtils; +import com.me.tft_02.soulbound.util.ItemUtils.ItemType; +import com.me.tft_02.soulbound.util.Permissions; + +public class InventoryListener implements Listener { + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + void onInventoryClick(InventoryClickEvent event) { + HumanEntity entity = event.getWhoClicked(); + ItemStack itemStack = event.getCurrentItem(); + + SlotType slotType = event.getSlotType(); + InventoryType inventoryType = event.getInventory().getType(); + + if (inventoryType == null) { + return; + } + + if (itemStack == null) { + return; + } + + if (entity instanceof Player) { + Player player = (Player) entity; + switch (slotType) { + case ARMOR: + new UpdateArmorTask(player).runTaskLater(Soulbound.p, 2); + return; + case CONTAINER: + ItemType itemType = ItemUtils.getItemType(itemStack); + switch (itemType) { + case BIND_ON_PICKUP: + ItemUtils.soulbindItem(player, itemStack); + return; + default: + return; + } + default: + if (ItemUtils.isEquipable(itemStack) && event.isShiftClick()) { + new UpdateArmorTask(player).runTaskLater(Soulbound.p, 2); + return; + } + break; + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onInventoryClickEvent(InventoryClickEvent event) { + HumanEntity entity = event.getWhoClicked(); + + if (!(entity instanceof Player)) { + return; + } + + Player player = (Player) entity; + ItemStack itemStack = event.getCurrentItem(); + InventoryType inventoryType = event.getInventory().getType(); + + if (inventoryType == null) { + return; + } + + ItemType itemType = ItemUtils.getItemType(itemStack); + + if (itemType != ItemType.SOULBOUND) { + return; + } + + if (!Config.getInstance().getAllowItemStoring() && !(inventoryType == InventoryType.CRAFTING)) { + event.setCancelled(true); + } + + if (ItemUtils.isBindedPlayer(player, itemStack) || Permissions.pickupBypass(player)) { + return; + } + + event.setCancelled(true); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onInventoryMoveEvent(InventoryMoveItemEvent event) { + ItemStack itemStack = event.getItem(); + ItemType itemType = ItemUtils.getItemType(itemStack); + + if (itemType == ItemType.SOULBOUND) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onInventoryPickupItemEvent(InventoryPickupItemEvent event) { + ItemStack itemStack = event.getItem().getItemStack(); + ItemType itemType = ItemUtils.getItemType(itemStack); + + if (itemType == ItemType.SOULBOUND) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onInventoryOpen(InventoryOpenEvent event) { + HumanEntity humanEntity = event.getPlayer(); + Inventory inventory = event.getInventory(); + + if (!(humanEntity instanceof Player)) { + return; + } + + Player player = (Player) humanEntity; + + for (ItemStack itemStack : inventory.getContents()) { + if (itemStack != null && ItemsConfig.getInstance().isActionItem(itemStack, ActionType.OPEN_CHEST)) { + ItemUtils.soulbindItem(player, itemStack); + } + } + } + + /** + * Check CraftItemEvent events. + * + * @param event The event to check + */ + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + private void onCraftItem(CraftItemEvent event) { + HumanEntity humanEntity = event.getWhoClicked(); + + if (!(humanEntity instanceof Player)) { + return; + } + + Player player = (Player) humanEntity; + + ItemStack itemStack = event.getRecipe().getResult(); + + if (ItemsConfig.getInstance().isActionItem(itemStack, ActionType.CRAFT)) { + event.getInventory().setResult(ItemUtils.soulbindItem(player, itemStack)); + } + } + + /** + * Check EnchantItemEvent events. + * + * @param event The event to check + */ + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + private void onEnchantItem(EnchantItemEvent event) { + Player player = event.getEnchanter(); + ItemStack itemStack = event.getItem(); + + if (ItemUtils.isBindOnUse(itemStack)) { + ItemUtils.soulbindItem(player, itemStack); + return; + } + } +} diff --git a/src/main/java/com/me/tft_02/soulbound/listeners/PlayerListener.java b/src/main/java/com/me/tft_02/soulbound/listeners/PlayerListener.java new file mode 100644 index 0000000..9ab7b26 --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/listeners/PlayerListener.java @@ -0,0 +1,284 @@ +package com.me.tft_02.soulbound.listeners; + +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.ChatColor; +import org.bukkit.Sound; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; +import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.event.player.PlayerFishEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerPickupItemEvent; +import org.bukkit.event.player.PlayerRespawnEvent; +import org.bukkit.event.player.PlayerShearEntityEvent; +import org.bukkit.event.server.ServerCommandEvent; +import org.bukkit.inventory.ItemStack; + +import com.me.tft_02.soulbound.Soulbound; +import com.me.tft_02.soulbound.config.Config; +import com.me.tft_02.soulbound.config.ItemsConfig; +import com.me.tft_02.soulbound.datatypes.ActionType; +import com.me.tft_02.soulbound.runnables.SoulbindInventoryTask; +import com.me.tft_02.soulbound.runnables.UpdateArmorTask; +import com.me.tft_02.soulbound.runnables.UpdateInventoryTask; +import com.me.tft_02.soulbound.util.CommandUtils; +import com.me.tft_02.soulbound.util.DurabilityUtils; +import com.me.tft_02.soulbound.util.ItemUtils; +import com.me.tft_02.soulbound.util.Permissions; +import com.me.tft_02.soulbound.util.PlayerData; + +public class PlayerListener implements Listener { + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + private void onPlayerJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + + if (Soulbound.p.isUpdateAvailable() && Permissions.updateCheck(player)) { + player.sendMessage(ChatColor.GOLD + "Soulbound is outdated!"); + player.sendMessage(ChatColor.AQUA + "http://dev.bukkit.org/server-mods/Soulbound/"); + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + private void onItemPickup(PlayerPickupItemEvent event) { + Player player = event.getPlayer(); + Item item = event.getItem(); + ItemStack itemStack = item.getItemStack(); + + if (ItemUtils.isSoulbound(itemStack) && !ItemUtils.isBindedPlayer(player, itemStack)) { + if (Permissions.pickupBypass(player)) { + return; + } + + event.setCancelled(true); + return; + } + + if (ItemUtils.isBindOnPickup(itemStack)) { + ItemUtils.soulbindItem(player, itemStack); + return; + } + + if (ItemsConfig.getInstance().isActionItem(itemStack, ActionType.PICKUP_ITEM)) { + ItemUtils.soulbindItem(player, itemStack); + return; + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + private void onItemDrop(PlayerDropItemEvent event) { + Player player = event.getPlayer(); + Item item = event.getItemDrop(); + ItemStack itemStack = item.getItemStack(); + + if (Config.getInstance().getPreventItemDrop()) { + if (ItemUtils.isSoulbound(itemStack) && ItemUtils.isBindedPlayer(player, itemStack)) { + item.setPickupDelay(2 * 20); + event.setCancelled(true); + new UpdateInventoryTask(player).runTask(Soulbound.p); + } + return; + } + + if (Config.getInstance().getDeleteOnDrop()) { + player.playSound(player.getLocation(), Sound.ITEM_BREAK, 1.0F, 1.0F); + event.getItemDrop().remove(); + return; + } + + if (ItemsConfig.getInstance().isActionItem(itemStack, ActionType.DROP_ITEM)) { + ItemUtils.soulbindItem(player, itemStack); + return; + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + private void onPlayerDeath(PlayerDeathEvent event) { + Player player = event.getEntity(); + + boolean deleteOnDeath = Permissions.deleteOnDeath(player); + boolean keepOnDeath = Permissions.keepOnDeath(player); + + List items = new ArrayList(); + + if (!keepOnDeath && !deleteOnDeath) { + return; + } + + for (ItemStack item : new ArrayList(event.getDrops())) { + if (ItemUtils.isSoulbound(item) && ItemUtils.isBindedPlayer(player, item)) { + if (keepOnDeath) { + items.add(item); + event.getDrops().remove(item); + } + else if (deleteOnDeath) { + event.getDrops().remove(item); + } + } + } + + PlayerData.storeItemsDeath(player, items); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + private void onPlayerRespawn(PlayerRespawnEvent event) { + new SoulbindInventoryTask(event.getPlayer(), ActionType.RESPAWN).runTask(Soulbound.p); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + private void onPlayerRespawnHighest(PlayerRespawnEvent event) { + Player player = event.getPlayer(); + boolean keepOnDeath = Permissions.keepOnDeath(player); + + if (!keepOnDeath) { + return; + } + + List items = new ArrayList(); + items = PlayerData.retrieveItemsDeath(player); + if (items != null) { + for (ItemStack item : items) { + player.getInventory().addItem(item); + } + } + + new UpdateInventoryTask(player).runTask(Soulbound.p); + } + + /** + * Watch PlayerInteract events. + * + * @param event The event to watch + */ + @EventHandler(priority = EventPriority.LOW) + public void onPlayerInteract(PlayerInteractEvent event) { + Player player = event.getPlayer(); + Action action = event.getAction(); + ItemStack inHand = player.getItemInHand(); + + switch (action) { + case RIGHT_CLICK_BLOCK: + case RIGHT_CLICK_AIR: + case LEFT_CLICK_AIR: + case LEFT_CLICK_BLOCK: + if (ItemUtils.isEquipable(inHand)) { + new UpdateArmorTask(player).runTaskLater(Soulbound.p, 2); + } + else if (ItemUtils.isBindOnUse(inHand)) { + ItemUtils.soulbindItem(player, inHand); + } + default: + break; + } + } + + /** + * Monitor PlayerFishEvent events. + * + * @param event The event to monitor + */ + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + private void onPlayerFish(PlayerFishEvent event) { + ItemStack itemInHand = event.getPlayer().getItemInHand(); + + DurabilityUtils.handleInfiniteDurability(itemInHand); + } + + /** + * Monitor PlayerShearEntityEvent events. + * + * @param event The event to monitor + */ + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + private void onPlayerShearEntity(PlayerShearEntityEvent event) { + ItemStack itemInHand = event.getPlayer().getItemInHand(); + + DurabilityUtils.handleInfiniteDurability(itemInHand); + } + + /** + * Watch PlayerCommandPreprocessEvent events. + * + * @param event The event to watch + */ + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onPlayerCommand(PlayerCommandPreprocessEvent event) { + Player player = event.getPlayer(); + ItemStack itemStack = player.getItemInHand(); + String command = event.getMessage(); + + if (ItemUtils.isSoulbound(itemStack) && Config.getInstance().getBlockedCommands().contains(command)) { + player.sendMessage(ChatColor.RED + "You're not allowed to use " + ChatColor.GOLD + command + ChatColor.RED + " command while holding a Soulbound item."); + event.setCancelled(true); + } + } + + /** + * Monitor PlayerCommandPreprocessEvent events. + * + * @param event The event to monitor + */ + @SuppressWarnings("deprecation") + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPlayerCommandMonitor(PlayerCommandPreprocessEvent event) { + Player player = event.getPlayer(); + ItemStack inHand = player.getItemInHand(); + String command = event.getMessage(); + String[] args = CommandUtils.extractArgs(command); + + if (!ItemUtils.isSoulbound(inHand) && Config.getInstance().getBindCommands().contains(command)) { + ItemUtils.soulbindItem(player, inHand); + return; + } + + if (command.contains("kit")) { + Player target; + + if (args.length >= 2) { + target = Soulbound.p.getServer().getPlayer(args[1]); + } + else { + target = player; + } + + if (target == null) { + return; + } + + new SoulbindInventoryTask(target, ActionType.KIT).runTask(Soulbound.p); + } + } + + /** + * Monitor ServerCommandEvent events. + * + * @param event The event to monitor + */ + @SuppressWarnings("deprecation") + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onServerCommand(ServerCommandEvent event) { + String command = event.getCommand(); + String[] args = CommandUtils.extractArgs(command); + + if (!command.contains("kit")) { + return; + } + + if (args.length < 2) { + return; + } + + Player target = Soulbound.p.getServer().getPlayer(args[1]); + + new SoulbindInventoryTask(target, ActionType.KIT).runTask(Soulbound.p); + } +} diff --git a/src/main/java/com/me/tft_02/soulbound/listeners/SelfListener.java b/src/main/java/com/me/tft_02/soulbound/listeners/SelfListener.java new file mode 100644 index 0000000..2e1c4e6 --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/listeners/SelfListener.java @@ -0,0 +1,38 @@ +package com.me.tft_02.soulbound.listeners; + +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import com.me.tft_02.soulbound.events.SoulbindItemEvent; +import com.me.tft_02.soulbound.util.ItemUtils; +import com.me.tft_02.soulbound.util.Permissions; + +public class SelfListener implements Listener { + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + private void onItemSoulbound(SoulbindItemEvent event) { + Player player = event.getPlayer(); + Inventory inventory = player.getInventory(); + int maxAmount = Permissions.getSoulbindMaximum(player); + + if (maxAmount < 0) { + return; + } + + int count = 0; + for (ItemStack itemStack : inventory.getContents()) { + if (itemStack != null && ItemUtils.isSoulbound(itemStack)) { + count++; + } + } + if (count >= maxAmount) { + player.sendMessage(ChatColor.RED + "Cannot Soulbind any more items, maximum amount reached! " + ChatColor.GOLD + "(" + maxAmount + ")"); + event.setCancelled(true); + } + } +} diff --git a/src/main/java/com/me/tft_02/soulbound/runnables/SoulbindInventoryTask.java b/src/main/java/com/me/tft_02/soulbound/runnables/SoulbindInventoryTask.java new file mode 100644 index 0000000..53ecb9e --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/runnables/SoulbindInventoryTask.java @@ -0,0 +1,34 @@ +package com.me.tft_02.soulbound.runnables; + +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitRunnable; + +import com.me.tft_02.soulbound.config.ItemsConfig; +import com.me.tft_02.soulbound.datatypes.ActionType; +import com.me.tft_02.soulbound.util.ItemUtils; + +public class SoulbindInventoryTask extends BukkitRunnable { + private Player player; + private ActionType actionType; + + public SoulbindInventoryTask(Player player, ActionType actionType) { + this.player = player; + this.actionType = actionType; + } + + @Override + public void run() { + for (ItemStack itemStack : player.getInventory().getContents()) { + if (itemStack != null && ItemsConfig.getInstance().isActionItem(itemStack, actionType)) { + ItemUtils.soulbindItem(player, itemStack); + } + } + + for (ItemStack itemStack : player.getInventory().getArmorContents()) { + if (itemStack != null && ItemsConfig.getInstance().isActionItem(itemStack, actionType)) { + ItemUtils.soulbindItem(player, itemStack); + } + } + } +} diff --git a/src/main/java/com/me/tft_02/soulbound/runnables/UpdateArmorTask.java b/src/main/java/com/me/tft_02/soulbound/runnables/UpdateArmorTask.java new file mode 100644 index 0000000..550a64a --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/runnables/UpdateArmorTask.java @@ -0,0 +1,31 @@ +package com.me.tft_02.soulbound.runnables; + +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitRunnable; + +import com.me.tft_02.soulbound.util.ItemUtils; +import com.me.tft_02.soulbound.util.ItemUtils.ItemType; + +public class UpdateArmorTask extends BukkitRunnable { + private Player player; + + public UpdateArmorTask(Player player) { + this.player = player; + } + + @Override + public void run() { + handleBindOnEquip(player); + } + + public void handleBindOnEquip(Player player) { + for (ItemStack armor : player.getInventory().getArmorContents()) { + if (armor != null && ItemUtils.getItemType(armor) == ItemType.BIND_ON_EQUIP) { + ItemUtils.soulbindItem(player, armor); + } + } + + player.getInventory().setArmorContents(player.getInventory().getArmorContents()); + } +} diff --git a/src/main/java/com/me/tft_02/soulbound/runnables/UpdateInventoryTask.java b/src/main/java/com/me/tft_02/soulbound/runnables/UpdateInventoryTask.java new file mode 100644 index 0000000..4cdcacb --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/runnables/UpdateInventoryTask.java @@ -0,0 +1,20 @@ +package com.me.tft_02.soulbound.runnables; + +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +public class UpdateInventoryTask extends BukkitRunnable { + private Player player; + + public UpdateInventoryTask(Player player) { + this.player = player; + } + + @SuppressWarnings("deprecation") + @Override + public void run() { + if (player.isValid()) { + player.updateInventory(); + } + } +} diff --git a/src/main/java/com/me/tft_02/soulbound/util/CommandUtils.java b/src/main/java/com/me/tft_02/soulbound/util/CommandUtils.java new file mode 100644 index 0000000..303e539 --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/util/CommandUtils.java @@ -0,0 +1,38 @@ +package com.me.tft_02.soulbound.util; + +import org.bukkit.ChatColor; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public final class CommandUtils { + + public static String[] extractArgs(String command) { + String[] args = {""}; + String[] split = command.split(" ", 2); + + if (split.length > 1) { + args = split[1].split(" "); + } + + return args; + } + + public static boolean noConsoleUsage(CommandSender sender) { + if (sender instanceof Player) { + return false; + } + + sender.sendMessage("This command does not support console usage."); + return true; + } + + public static boolean isOffline(CommandSender sender, OfflinePlayer player) { + if (player != null && player.isOnline()) { + return false; + } + + sender.sendMessage(ChatColor.RED + "This command does not work for offline players."); + return true; + } +} diff --git a/src/main/java/com/me/tft_02/soulbound/util/DurabilityUtils.java b/src/main/java/com/me/tft_02/soulbound/util/DurabilityUtils.java new file mode 100644 index 0000000..1193382 --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/util/DurabilityUtils.java @@ -0,0 +1,15 @@ +package com.me.tft_02.soulbound.util; + +import org.bukkit.inventory.ItemStack; + +import com.me.tft_02.soulbound.config.Config; + +public class DurabilityUtils { + + public static void handleInfiniteDurability(ItemStack itemStack) { + if (Config.getInstance().getInfiniteDurability() && ItemUtils.isSoulbound(itemStack)) { + itemStack.setDurability((short) 0); + return; + } + } +} diff --git a/src/main/java/com/me/tft_02/soulbound/util/ItemUtils.java b/src/main/java/com/me/tft_02/soulbound/util/ItemUtils.java new file mode 100644 index 0000000..cd5c11c --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/util/ItemUtils.java @@ -0,0 +1,435 @@ +package com.me.tft_02.soulbound.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import com.me.tft_02.soulbound.Soulbound; +import com.me.tft_02.soulbound.config.Config; +import com.me.tft_02.soulbound.events.SoulbindItemEvent; + +public class ItemUtils { + public enum ItemType { + NORMAL, + SOULBOUND, + BIND_ON_PICKUP, + BIND_ON_USE, + BIND_ON_EQUIP; + } + + public static ItemStack soulbindItem(Player player, ItemStack itemStack) { + if (itemStack == null) { + return itemStack; + } + + if (isSoulbound(itemStack)) { + return itemStack; + } + + SoulbindItemEvent soulbindItemEvent = new SoulbindItemEvent(player, itemStack); + Soulbound.p.getServer().getPluginManager().callEvent(soulbindItemEvent); + itemStack = soulbindItemEvent.getItemStack(); + + if (soulbindItemEvent.isCancelled()) { + return itemStack; + } + + ItemMeta itemMeta = itemStack.getItemMeta(); + List itemLore = new ArrayList(); + + if (itemMeta.hasLore()) { + List oldLore = itemMeta.getLore(); + oldLore.remove(ChatColor.DARK_RED + "Bind on Pickup"); + oldLore.remove(ChatColor.DARK_RED + "Bind on Equip"); + oldLore.remove(ChatColor.DARK_RED + "Bind on Use"); + itemLore.addAll(oldLore); + } + + itemLore.add(Misc.SOULBOUND_TAG + StringUtils.hideUUID(player.getUniqueId())); + + if (Config.getInstance().getShowNameInLore()) { + itemLore.add(player.getName()); + } + itemMeta.setLore(itemLore); + itemStack.setItemMeta(itemMeta); + return itemStack; + } + + public static ItemStack bopItem(ItemStack itemStack) { + if (itemStack == null) { + return itemStack; + } + + if (isBindOnPickup(itemStack)) { + return itemStack; + } + + ItemMeta itemMeta = itemStack.getItemMeta(); + + List itemLore = new ArrayList(); + if (itemMeta.hasLore()) { + itemLore.addAll(itemMeta.getLore()); + } + itemLore.add(ChatColor.DARK_RED + "Bind on Pickup"); + itemMeta.setLore(itemLore); + itemStack.setItemMeta(itemMeta); + return itemStack; + } + + public static ItemStack boeItem(ItemStack itemStack) { + if (itemStack == null) { + return itemStack; + } + + if (isBindOnEquip(itemStack)) { + return itemStack; + } + + ItemMeta itemMeta = itemStack.getItemMeta(); + + List itemLore = new ArrayList(); + if (itemMeta.hasLore()) { + itemLore.addAll(itemMeta.getLore()); + } + itemLore.add(ChatColor.DARK_RED + "Bind on Equip"); + itemMeta.setLore(itemLore); + itemStack.setItemMeta(itemMeta); + return itemStack; + } + + public static ItemStack bouItem(ItemStack itemStack) { + if (itemStack == null) { + return itemStack; + } + + if (isBindOnUse(itemStack)) { + return itemStack; + } + + ItemMeta itemMeta = itemStack.getItemMeta(); + + List itemLore = new ArrayList(); + if (itemMeta.hasLore()) { + itemLore.addAll(itemMeta.getLore()); + } + itemLore.add(ChatColor.DARK_RED + "Bind on Use"); + itemMeta.setLore(itemLore); + itemStack.setItemMeta(itemMeta); + return itemStack; + } + + public static ItemStack unbindItem(ItemStack itemStack) { + if (itemStack == null) { + return null; + } + + ItemMeta itemMeta = itemStack.getItemMeta(); + if (itemMeta.hasLore() && isSoulbound(itemStack)) { + List oldLore = itemMeta.getLore(); + int loreSize = oldLore.size(); + + if (loreSize <= 2) { + itemMeta.setLore(null); + itemStack.setItemMeta(itemMeta); + return itemStack; + } + + List itemLore = new ArrayList(); + itemLore.addAll(oldLore); + int index = StringUtils.getIndexOfSoulbound(itemLore); + if (index == -1){ + return null; + } + itemLore.remove(index); + if (index itemLore = itemMeta.getLore(); + for (String lore : itemLore) { + if (lore.equalsIgnoreCase(Misc.SOULBOUND_TAG)) { + return true; + } + } + } + return false; + } + + public static boolean isBindOnPickup(ItemStack itemStack) { + if (!itemStack.hasItemMeta()) { + return false; + } + + ItemMeta itemMeta = itemStack.getItemMeta(); + if (itemMeta.hasLore()) { + List itemLore = itemMeta.getLore(); + if (itemLore.contains(ChatColor.DARK_RED + "Bind on Pickup")) { + return true; + } + } + return false; + } + + public static boolean isBindOnUse(ItemStack itemStack) { + if (!itemStack.hasItemMeta()) { + return false; + } + + ItemMeta itemMeta = itemStack.getItemMeta(); + if (itemMeta.hasLore()) { + List itemLore = itemMeta.getLore(); + if (itemLore.contains(ChatColor.DARK_RED + "Bind on Use")) { + return true; + } + } + return false; + } + + public static boolean isBindOnEquip(ItemStack itemStack) { + if (!itemStack.hasItemMeta()) { + return false; + } + + ItemMeta itemMeta = itemStack.getItemMeta(); + if (itemMeta.hasLore()) { + List itemLore = itemMeta.getLore(); + if (itemLore.contains(ChatColor.DARK_RED + "Bind on Equip")) { + return true; + } + } + return false; + } + + public static boolean isBindedPlayer(Player player, ItemStack itemStack) { + updateOldLore(player, itemStack); + checkNameChange(player, itemStack); + List itemLore = itemStack.getItemMeta().getLore(); + + Soulbound.p.debug("UUID MATCH? " + player.getUniqueId().equals(StringUtils.readUUIDFromLore(itemLore))); + Soulbound.p.debug("NAME MATCH? " + itemLore.contains(player.getName())); + + return player.getUniqueId().equals(StringUtils.readUUIDFromLore(itemLore)) || itemLore.contains(player.getName()); + } + + private static ItemStack updateOldLore(Player player, ItemStack itemStack) { + ItemMeta itemMeta = itemStack.getItemMeta(); + if (itemMeta != null) { + if (!itemMeta.hasLore()) { + return itemStack; + } + } + List itemLore = itemMeta.getLore(); + + if (itemLore.size() - 1 < StringUtils.getIndexOfSoulbound(itemLore) + 2) { + return itemStack; + } + + int indexOfSoulbound = StringUtils.getIndexOfSoulbound(itemLore); + int indexOfUniqueId = indexOfSoulbound + 2; + UUID uuid = null; + + if (itemLore.get(indexOfUniqueId).contains(player.getUniqueId().toString())) { + uuid = UUID.fromString(ChatColor.stripColor(itemLore.get(indexOfUniqueId))); + itemLore.remove(indexOfUniqueId); + } + + if (StringUtils.readUUIDFromLore(itemLore) == null && uuid != null) { + itemLore.set(indexOfSoulbound, Misc.SOULBOUND_TAG + StringUtils.hideUUID(uuid)); + } + + itemMeta.setLore(itemLore); + itemStack.setItemMeta(itemMeta); + return itemStack; + } + + private static ItemStack checkNameChange(Player player, ItemStack itemStack) { + ItemMeta itemMeta = itemStack.getItemMeta(); + List itemLore = itemMeta.getLore(); + + int indexName = StringUtils.getIndexOfSoulbound(itemLore) + 1; + + if (itemLore.size() >= 2 && itemLore.contains(player.getName()) && !Config.getInstance().getShowNameInLore()) { + Soulbound.p.debug("Item lore has a player name but that's disabled in config"); + Soulbound.p.debug("Going to remove what is at indexName " + indexName + " which is: " + itemLore.get(indexName)); + itemLore.remove(indexName); + itemMeta.setLore(itemLore); + itemStack.setItemMeta(itemMeta); + return itemStack; + } + + if (Config.getInstance().getShowNameInLore() && !itemLore.contains(player.getName()) && player.getUniqueId().equals(StringUtils.readUUIDFromLore(itemLore))) { + Soulbound.p.debug("Item lore doesn't have (correct) player name but it should and the UUIDs match"); + itemLore.add(indexName, player.getName()); + } + + itemMeta.setLore(itemLore); + itemStack.setItemMeta(itemMeta); + return itemStack; + } + + public static boolean isNormalItem(ItemStack itemStack) { + return !itemStack.hasItemMeta() && !itemStack.getItemMeta().hasLore() || ItemUtils.getItemType(itemStack) == ItemType.NORMAL; + } + + public static ItemType getItemType(ItemStack itemStack) { + if (itemStack == null) { + return ItemType.NORMAL; + } + else if (isSoulbound(itemStack)) { + return ItemType.SOULBOUND; + } + else if (isBindOnPickup(itemStack)) { + return ItemType.BIND_ON_PICKUP; + } + else if (isBindOnUse(itemStack)) { + return ItemType.BIND_ON_USE; + } + else if (isBindOnEquip(itemStack)) { + return ItemType.BIND_ON_EQUIP; + } + else { + return ItemType.NORMAL; + } + } + + /** + * Checks to see if an item is an equipable item. + * + * @param is Item to check + * + * @return true if the item is equipable, false otherwise + */ + public static boolean isEquipable(ItemStack is) { + return isMinecraftArmor(is) || is.getType() == Material.SKULL_ITEM || is.getType() == Material.JACK_O_LANTERN; + } + + /** + * Checks to see if an item is a wearable armor piece. + * + * @param is Item to check + * + * @return true if the item is armor, false otherwise + */ + public static boolean isMinecraftArmor(ItemStack is) { + return isLeatherArmor(is) || isGoldArmor(is) || isIronArmor(is) || isDiamondArmor(is) || isChainmailArmor(is); + } + + /** + * Checks to see if an item is a leather armor piece. + * + * @param is Item to check + * + * @return true if the item is leather armor, false otherwise + */ + public static boolean isLeatherArmor(ItemStack is) { + switch (is.getType()) { + case LEATHER_BOOTS: + case LEATHER_CHESTPLATE: + case LEATHER_HELMET: + case LEATHER_LEGGINGS: + return true; + + default: + return false; + } + } + + /** + * Checks to see if an item is a gold armor piece. + * + * @param is Item to check + * + * @return true if the item is gold armor, false otherwise + */ + public static boolean isGoldArmor(ItemStack is) { + switch (is.getType()) { + case GOLD_BOOTS: + case GOLD_CHESTPLATE: + case GOLD_HELMET: + case GOLD_LEGGINGS: + return true; + + default: + return false; + } + } + + /** + * Checks to see if an item is an iron armor piece. + * + * @param is Item to check + * + * @return true if the item is iron armor, false otherwise + */ + public static boolean isIronArmor(ItemStack is) { + switch (is.getType()) { + case IRON_BOOTS: + case IRON_CHESTPLATE: + case IRON_HELMET: + case IRON_LEGGINGS: + return true; + + default: + return false; + } + } + + /** + * Checks to see if an item is a diamond armor piece. + * + * @param is Item to check + * + * @return true if the item is diamond armor, false otherwise + */ + public static boolean isDiamondArmor(ItemStack is) { + switch (is.getType()) { + case DIAMOND_BOOTS: + case DIAMOND_CHESTPLATE: + case DIAMOND_HELMET: + case DIAMOND_LEGGINGS: + return true; + + default: + return false; + } + } + + /** + * Checks to see if an item is a chainmail armor piece. + * + * @param is Item to check + * + * @return true if the item is chainmail armor, false otherwise + */ + public static boolean isChainmailArmor(ItemStack is) { + switch (is.getType()) { + case CHAINMAIL_BOOTS: + case CHAINMAIL_CHESTPLATE: + case CHAINMAIL_HELMET: + case CHAINMAIL_LEGGINGS: + return true; + + default: + return false; + } + } +} diff --git a/src/main/java/com/me/tft_02/soulbound/util/LogFilter.java b/src/main/java/com/me/tft_02/soulbound/util/LogFilter.java new file mode 100644 index 0000000..506f319 --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/util/LogFilter.java @@ -0,0 +1,24 @@ +package com.me.tft_02.soulbound.util; + +import java.util.logging.Filter; +import java.util.logging.LogRecord; + +import com.me.tft_02.soulbound.Soulbound; + +public class LogFilter implements Filter { + private boolean debug; + + public LogFilter(Soulbound plugin) { + // Doing a config loading lite here, because we can't depend on the config loader to have loaded before any debug messages are sent + debug = plugin.getConfig().getBoolean("General.Verbose_Logging"); + } + + @Override + public boolean isLoggable(LogRecord record) { + if (record.getMessage().contains("[Debug]") && !debug) { + return false; + } + + return true; + } +} diff --git a/src/main/java/com/me/tft_02/soulbound/util/Misc.java b/src/main/java/com/me/tft_02/soulbound/util/Misc.java new file mode 100644 index 0000000..d014fe7 --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/util/Misc.java @@ -0,0 +1,7 @@ +package com.me.tft_02.soulbound.util; + +import org.bukkit.ChatColor; + +public class Misc { + public static String SOULBOUND_TAG = ChatColor.GOLD + "Soulbound"; +} diff --git a/src/main/java/com/me/tft_02/soulbound/util/Permissions.java b/src/main/java/com/me/tft_02/soulbound/util/Permissions.java new file mode 100644 index 0000000..535a8be --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/util/Permissions.java @@ -0,0 +1,40 @@ +package com.me.tft_02.soulbound.util; + +import org.bukkit.entity.Player; +import org.bukkit.permissions.Permissible; +import org.bukkit.permissions.PermissionAttachmentInfo; + +public class Permissions { + + public static boolean keepOnDeath(Permissible permissible) { + return permissible.hasPermission("soulbound.items.keep_on_death"); + } + + public static boolean deleteOnDeath(Permissible permissible) { + return permissible.hasPermission("soulbound.items.delete_on_death"); + } + + public static boolean pickupBypass(Permissible permissible) { + return permissible.hasPermission("soulbound.pickup.bypass"); + } + + public static boolean updateCheck(Permissible permissible) { + return permissible.hasPermission("soulbound.updatecheck"); + } + + public static int getSoulbindMaximum(Player player) { + String match = "soulbound.maximum_allowed."; + int amount = -1; + + for (PermissionAttachmentInfo permission : player + .getEffectivePermissions()) { + if (permission.getPermission().startsWith(match) + && permission.getValue()) { + amount = Integer.parseInt(permission.getPermission().split( + "[.]")[2]); + } + } + + return amount; + } +} diff --git a/src/main/java/com/me/tft_02/soulbound/util/PlayerData.java b/src/main/java/com/me/tft_02/soulbound/util/PlayerData.java new file mode 100644 index 0000000..ed0f049 --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/util/PlayerData.java @@ -0,0 +1,40 @@ +package com.me.tft_02.soulbound.util; + +import java.util.HashMap; +import java.util.List; + +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import com.me.tft_02.soulbound.Soulbound; + +public class PlayerData { + Soulbound plugin; + + public PlayerData(Soulbound instance) { + plugin = instance; + } + + public static HashMap> playerSoulboundItems = new HashMap>(); + + public static void storeItemsDeath(Player player, List items) { + String playerName = player.getName(); + + if (playerSoulboundItems.containsKey(playerName)) { + playerSoulboundItems.put(playerName, null); + } + + playerSoulboundItems.put(playerName, items); + } + + public static List retrieveItemsDeath(Player player) { + String playerName = player.getName(); + + if (playerSoulboundItems.containsKey(playerName)) { + return playerSoulboundItems.get(playerName); + } + else { + return null; + } + } +} diff --git a/src/main/java/com/me/tft_02/soulbound/util/StringUtils.java b/src/main/java/com/me/tft_02/soulbound/util/StringUtils.java new file mode 100644 index 0000000..e375dd5 --- /dev/null +++ b/src/main/java/com/me/tft_02/soulbound/util/StringUtils.java @@ -0,0 +1,49 @@ +package com.me.tft_02.soulbound.util; + +import java.util.List; +import java.util.UUID; + +import org.bukkit.ChatColor; + +public class StringUtils { + + public static int getIndexOfSoulbound(List itemLore) { + int index = -1; + + for (String line : itemLore) { + if (line.contains(Misc.SOULBOUND_TAG)) { + return itemLore.indexOf(line); + } + } + return index; + } + + public static UUID readUUIDFromLore(List itemLore) { + int index = getIndexOfSoulbound(itemLore); + + if (index == -1) { + return null; + } + + String line = itemLore.get(index); + line = line.substring(11); + + return readUUID(line); + } + + public static String hideUUID(UUID uuid) { + String string = uuid.toString(); + string = string.replaceAll("-", ChatColor.MAGIC.toString()); + StringBuilder formattedString = new StringBuilder(); + + for (int i = 0; i < string.length(); i++) { + formattedString.append(ChatColor.COLOR_CHAR).append(string.charAt(i)); + } + + return formattedString.toString(); + } + + public static UUID readUUID(String string) { + return UUID.fromString(string.replaceAll(ChatColor.MAGIC.toString(), "-").replaceAll("§", "")); + } +} diff --git a/src/main/java/config.yml b/src/main/java/config.yml new file mode 100644 index 0000000..aa38a82 --- /dev/null +++ b/src/main/java/config.yml @@ -0,0 +1,50 @@ +# +# Soulbound configuration +# Last updated on ${project.version}-b${BUILD_NUMBER} +# +##### + +# +# Settings for Soulbound in general +### +General: + # Allow Soulbound to report on basic anonymous usage + Stats_Tracking: true + + # Allow Soulbound to check if a new version is available + Update_Check: true + Prefer_Beta: false + + # Should Soulbound print out debug messages? + Verbose_Logging: false + + # Should Soulbound over-write configs to update, or make new ones ending in .new? + Config_Update_Overwrite: true + +Soulbound: + Show_Name_In_Lore: true + Feedback_Messages_Enabled: true + Prevent_Item_Drop: false + Delete_On_Drop: false + Allow_Item_Storing: true + Infinite_Durability: false + Blocked_Commands: + /blockedcommand + Commands_Bind_When_Used: + /enchant + +Dependency_Plugins: + DiabloDrops: + BindOnPickup: Legendary, Rare, Unidentified + BindOnUse: Magical + BindOnEquip: Set + EpicBossRecoded: + BindOnPickup: true + BindOnUse: false + BindOnEquip: false + LoreLocks: + Bind_Keys: true + MythicDrops: + BindOnPickup: common, uncommon, rare + BindOnUse: terric, netheric + BindOnEquip: endric \ No newline at end of file diff --git a/src/main/java/items.yml b/src/main/java/items.yml new file mode 100644 index 0000000..c2d6315 --- /dev/null +++ b/src/main/java/items.yml @@ -0,0 +1,35 @@ +# +# Soulbound item configuration +# +# Items in this config file will be automatically Soulbound to the player on certain +# actions, such as dropping the item or picking it up. +# +# You can add items under "Items" using the exact material name. You can add a data value using a | +# For example, WOOL|15 would be Material.WOOL with data value 15. +# +# The bare minimum of a Soulbound item is that it has Actions +# +# Name: This is the displayname of an item +# +# Lore: List one or more lines of lore here +# +# Actions: The actions when the item should be soulbound to the player. +# Valid values are: CRAFT, OPEN_CHEST, PICKUP_ITEM, DROP_ITEM, RESPAWN, KIT +# +# +### + +Items: + DIAMOND_SPADE: + Lore: + - Made from diamond, + - can dig dirt! + Actions: + - CRAFT + - OPEN_CHEST + + WOOL|15: + Name: Black Piece Of Wool + Actions: + - PICKUP_ITEM + - DROP_ITEM diff --git a/src/main/java/net/gravitydevelopment/updater/soulbound/Updater.java b/src/main/java/net/gravitydevelopment/updater/soulbound/Updater.java new file mode 100644 index 0000000..e019352 --- /dev/null +++ b/src/main/java/net/gravitydevelopment/updater/soulbound/Updater.java @@ -0,0 +1,558 @@ +/* + * Updater for Bukkit. + * + * This class provides the means to safely and easily update a plugin, or check to see if it is updated using dev.bukkit.org + */ + +package net.gravitydevelopment.updater.soulbound; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.Plugin; + +import com.me.tft_02.soulbound.config.Config; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.JSONValue; + +/** + * Check dev.bukkit.org to find updates for a given plugin, and download the updates if needed. + *

+ * VERY, VERY IMPORTANT: Because there are no standards for adding auto-update toggles in your plugin's config, this system provides NO CHECK WITH YOUR CONFIG to make sure the user has allowed auto-updating. + *
+ * It is a BUKKIT POLICY that you include a boolean value in your config that prevents the auto-updater from running AT ALL. + *
+ * If you fail to include this option in your config, your plugin will be REJECTED when you attempt to submit it to dev.bukkit.org. + *

+ * An example of a good configuration option would be something similar to 'auto-update: true' - if this value is set to false you may NOT run the auto-updater. + *
+ * If you are unsure about these rules, please read the plugin submission guidelines: http://goo.gl/8iU5l + * + * @author Gravity + * @version 2.0 + */ + +public class Updater { + + private Plugin plugin; + private UpdateType type; + private String versionName; + private String versionLink; + private String versionType; + private String versionGameVersion; + + private boolean announce; // Whether to announce file downloads + + private URL url; // Connecting to RSS + private File file; // The plugin's file + private Thread thread; // Updater thread + + private int id = -1; // Project's Curse ID + private String apiKey = null; // BukkitDev ServerMods API key + private static final String TITLE_VALUE = "name"; // Gets remote file's title + private static final String LINK_VALUE = "downloadUrl"; // Gets remote file's download link + private static final String TYPE_VALUE = "releaseType"; // Gets remote file's release type + private static final String VERSION_VALUE = "gameVersion"; // Gets remote file's build version + private static final String QUERY = "/servermods/files?projectIds="; // Path to GET + private static final String HOST = "https://api.curseforge.com"; // Slugs will be appended to this to get to the project's RSS feed + + private static final int BYTE_SIZE = 1024; // Used for downloading files + private YamlConfiguration config; // Config file + private String updateFolder;// The folder that downloads will be placed in + private Updater.UpdateResult result = Updater.UpdateResult.SUCCESS; // Used for determining the outcome of the update process + + /** + * Gives the dev the result of the update process. Can be obtained by called getResult(). + */ + public enum UpdateResult { + /** + * The updater found an update, and has readied it to be loaded the next time the server restarts/reloads. + */ + SUCCESS, + /** + * The updater did not find an update, and nothing was downloaded. + */ + NO_UPDATE, + /** + * The server administrator has disabled the updating system + */ + DISABLED, + /** + * The updater found an update, but was unable to download it. + */ + FAIL_DOWNLOAD, + /** + * For some reason, the updater was unable to contact dev.bukkit.org to download the file. + */ + FAIL_DBO, + /** + * When running the version check, the file on DBO did not contain the a version in the format 'vVersion' such as 'v1.0'. + */ + FAIL_NOVERSION, + /** + * The id provided by the plugin running the updater was invalid and doesn't exist on DBO. + */ + FAIL_BADID, + /** + * The server administrator has improperly configured their API key in the configuration + */ + FAIL_APIKEY, + /** + * The updater found an update, but because of the UpdateType being set to NO_DOWNLOAD, it wasn't downloaded. + */ + UPDATE_AVAILABLE + } + + /** + * Allows the dev to specify the type of update that will be run. + */ + public enum UpdateType { + /** + * Run a version check, and then if the file is out of date, download the newest version. + */ + DEFAULT, + /** + * Don't run a version check, just find the latest update and download it. + */ + NO_VERSION_CHECK, + /** + * Get information about the version and the download size, but don't actually download anything. + */ + NO_DOWNLOAD + } + + /** + * Initialize the updater + * + * @param plugin The plugin that is checking for an update. + * @param id The dev.bukkit.org id of the project + * @param file The file that the plugin is running from, get this by doing this.getFile() from within your main class. + * @param type Specify the type of update this will be. See {@link UpdateType} + * @param announce True if the program should announce the progress of new updates in console + */ + public Updater(Plugin plugin, int id, File file, UpdateType type, boolean announce) { + this.plugin = plugin; + this.type = type; + this.announce = announce; + this.file = file; + this.id = id; + this.updateFolder = plugin.getServer().getUpdateFolder(); + + final File pluginFile = plugin.getDataFolder().getParentFile(); + final File updaterFile = new File(pluginFile, "Updater"); + final File updaterConfigFile = new File(updaterFile, "config.yml"); + + if (!updaterFile.exists()) { + updaterFile.mkdir(); + } + if (!updaterConfigFile.exists()) { + try { + updaterConfigFile.createNewFile(); + } + catch (final IOException e) { + plugin.getLogger().severe("The updater could not create a configuration in " + updaterFile.getAbsolutePath()); + e.printStackTrace(); + } + } + this.config = YamlConfiguration.loadConfiguration(updaterConfigFile); + + this.config.options().header("This configuration file affects all plugins using the Updater system (version 2+ - http://forums.bukkit.org/threads/96681/ )" + '\n' + + "If you wish to use your API key, read http://wiki.bukkit.org/ServerMods_API and place it below." + '\n' + + "Some updating systems will not adhere to the disabled value, but these may be turned off in their plugin's configuration."); + this.config.addDefault("api-key", "PUT_API_KEY_HERE"); + this.config.addDefault("disable", false); + + if (this.config.get("api-key", null) == null) { + this.config.options().copyDefaults(true); + try { + this.config.save(updaterConfigFile); + } + catch (final IOException e) { + plugin.getLogger().severe("The updater could not save the configuration in " + updaterFile.getAbsolutePath()); + e.printStackTrace(); + } + } + + if (this.config.getBoolean("disable")) { + this.result = UpdateResult.DISABLED; + return; + } + + String key = this.config.getString("api-key"); + if (key.equalsIgnoreCase("PUT_API_KEY_HERE") || key.equals("")) { + key = null; + } + + this.apiKey = key; + + try { + this.url = new URL(Updater.HOST + Updater.QUERY + id); + } + catch (final MalformedURLException e) { + plugin.getLogger().severe("The project ID provided for updating, " + id + " is invalid."); + this.result = UpdateResult.FAIL_BADID; + e.printStackTrace(); + } + + this.thread = new Thread(new UpdateRunnable()); + this.thread.start(); + } + + /** + * Get the result of the update process. + */ + public Updater.UpdateResult getResult() { + this.waitForThread(); + return this.result; + } + + /** + * Get the latest version's release type (release, beta, or alpha). + */ + public String getLatestType() { + this.waitForThread(); + return this.versionType; + } + + /** + * Get the latest version's game version. + */ + public String getLatestGameVersion() { + this.waitForThread(); + return this.versionGameVersion; + } + + /** + * Get the latest version's name. + */ + public String getLatestName() { + this.waitForThread(); + return this.versionName; + } + + /** + * Get the latest version's file link. + */ + public String getLatestFileLink() { + this.waitForThread(); + return this.versionLink; + } + + /** + * As the result of Updater output depends on the thread's completion, it is necessary to wait for the thread to finish + * before allowing anyone to check the result. + */ + private void waitForThread() { + if ((this.thread != null) && this.thread.isAlive()) { + try { + this.thread.join(); + } + catch (final InterruptedException e) { + e.printStackTrace(); + } + } + } + + /** + * Save an update from dev.bukkit.org into the server's update folder. + */ + private void saveFile(File folder, String file, String u) { + if (!folder.exists()) { + folder.mkdir(); + } + BufferedInputStream in = null; + FileOutputStream fout = null; + try { + // Download the file + final URL url = new URL(u); + final int fileLength = url.openConnection().getContentLength(); + in = new BufferedInputStream(url.openStream()); + fout = new FileOutputStream(folder.getAbsolutePath() + "/" + file); + + final byte[] data = new byte[Updater.BYTE_SIZE]; + int count; + if (this.announce) { + this.plugin.getLogger().info("About to download a new update: " + this.versionName); + } + long downloaded = 0; + while ((count = in.read(data, 0, Updater.BYTE_SIZE)) != -1) { + downloaded += count; + fout.write(data, 0, count); + final int percent = (int) ((downloaded * 100) / fileLength); + if (this.announce && ((percent % 10) == 0)) { + this.plugin.getLogger().info("Downloading update: " + percent + "% of " + fileLength + " bytes."); + } + } + //Just a quick check to make sure we didn't leave any files from last time... + for (final File xFile : new File(this.plugin.getDataFolder().getParent(), this.updateFolder).listFiles()) { + if (xFile.getName().endsWith(".zip")) { + xFile.delete(); + } + } + // Check to see if it's a zip file, if it is, unzip it. + final File dFile = new File(folder.getAbsolutePath() + "/" + file); + if (dFile.getName().endsWith(".zip")) { + // Unzip + this.unzip(dFile.getCanonicalPath()); + } + if (this.announce) { + this.plugin.getLogger().info("Finished updating."); + } + } + catch (final Exception ex) { + this.plugin.getLogger().warning("The auto-updater tried to download a new update, but was unsuccessful."); + this.result = Updater.UpdateResult.FAIL_DOWNLOAD; + } + finally { + try { + if (in != null) { + in.close(); + } + if (fout != null) { + fout.close(); + } + } + catch (final Exception ex) { + } + } + } + + /** + * Part of Zip-File-Extractor, modified by Gravity for use with Bukkit + */ + private void unzip(String file) { + try { + final File fSourceZip = new File(file); + final String zipPath = file.substring(0, file.length() - 4); + ZipFile zipFile = new ZipFile(fSourceZip); + Enumeration e = zipFile.entries(); + while (e.hasMoreElements()) { + ZipEntry entry = e.nextElement(); + File destinationFilePath = new File(zipPath, entry.getName()); + destinationFilePath.getParentFile().mkdirs(); + if (entry.isDirectory()) { + continue; + } + + final BufferedInputStream bis = new BufferedInputStream(zipFile.getInputStream(entry)); + int b; + final byte buffer[] = new byte[Updater.BYTE_SIZE]; + final FileOutputStream fos = new FileOutputStream(destinationFilePath); + final BufferedOutputStream bos = new BufferedOutputStream(fos, Updater.BYTE_SIZE); + while ((b = bis.read(buffer, 0, Updater.BYTE_SIZE)) != -1) { + bos.write(buffer, 0, b); + } + bos.flush(); + bos.close(); + bis.close(); + final String name = destinationFilePath.getName(); + if (name.endsWith(".jar") && this.pluginFile(name)) { + destinationFilePath.renameTo(new File(this.plugin.getDataFolder().getParent(), this.updateFolder + "/" + name)); + } + + entry = null; + destinationFilePath = null; + } + e = null; + zipFile.close(); + zipFile = null; + + // Move any plugin data folders that were included to the right place, Bukkit won't do this for us. + for (final File dFile : new File(zipPath).listFiles()) { + if (dFile.isDirectory()) { + if (this.pluginFile(dFile.getName())) { + final File oFile = new File(this.plugin.getDataFolder().getParent(), dFile.getName()); // Get current dir + final File[] contents = oFile.listFiles(); // List of existing files in the current dir + for (final File cFile : dFile.listFiles()) // Loop through all the files in the new dir + { + boolean found = false; + for (final File xFile : contents) // Loop through contents to see if it exists + { + if (xFile.getName().equals(cFile.getName())) { + found = true; + break; + } + } + if (!found) { + // Move the new file into the current dir + cFile.renameTo(new File(oFile.getCanonicalFile() + "/" + cFile.getName())); + } + else { + // This file already exists, so we don't need it anymore. + cFile.delete(); + } + } + } + } + dFile.delete(); + } + new File(zipPath).delete(); + fSourceZip.delete(); + } + catch (final IOException ex) { + this.plugin.getLogger().warning("The auto-updater tried to unzip a new update file, but was unsuccessful."); + this.result = Updater.UpdateResult.FAIL_DOWNLOAD; + ex.printStackTrace(); + } + new File(file).delete(); + } + + /** + * Check if the name of a jar is one of the plugins currently installed, used for extracting the correct files out of a zip. + */ + private boolean pluginFile(String name) { + for (final File file : new File("plugins").listFiles()) { + if (file.getName().equals(name)) { + return true; + } + } + return false; + } + + /** + * Check to see if the program should continue by evaluation whether the plugin is already updated, or shouldn't be updated + */ + private boolean versionCheck(String title) { + if (type != UpdateType.NO_VERSION_CHECK) { + String version = plugin.getDescription().getVersion(); + title = title.substring(1); + + String[] oldTokens = version.split("-"); + String[] newTokens = title.split("-"); + + int oldVersion = Integer.parseInt(oldTokens[0].replaceAll("[.]", "")); + int newVersion = Integer.parseInt(newTokens[0].replaceAll("[.]", "")); + + // Check versions + if (oldVersion < newVersion) { + return true; + } + + // Check release vs. beta & SNAPSHOT + if (newTokens.length == 1 && oldTokens.length == 3 && oldVersion == newVersion) { + return true; + } + + // Check beta vs. SNAPSHOT + if (version.contains("SNAPSHOT") && title.contains("beta")) { + if (Integer.parseInt(oldTokens[1].substring(8)) <= Integer.parseInt(newTokens[1].substring(4))) { + return true; + } + + result = UpdateResult.NO_UPDATE; + return false; + } + + // Check beta vs. beta + if (version.contains("beta") && title.contains("beta")) { + if (Integer.parseInt(oldTokens[1].substring(4)) < Integer.parseInt(newTokens[1].substring(4))) { + return true; + } + + result = UpdateResult.NO_UPDATE; + return false; + } + + if (oldTokens.length == 3 && !version.contains("beta") && !version.contains("SNAPSHOT")) { + plugin.getLogger().warning("Could not get information about this Soulbound version; perhaps you are running a custom one?"); + result = UpdateResult.FAIL_NOVERSION; + return false; + } + + result = UpdateResult.NO_UPDATE; + return false; + } + + return true; + } + + private boolean read() { + try { + final URLConnection conn = this.url.openConnection(); + conn.setConnectTimeout(5000); + + if (this.apiKey != null) { + conn.addRequestProperty("X-API-Key", this.apiKey); + } + conn.addRequestProperty("User-Agent", "Updater (by Gravity)"); + + conn.setDoOutput(true); + + final BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); + final String response = reader.readLine(); + + final JSONArray array = (JSONArray) JSONValue.parse(response); + + if (array.size() == 0) { + this.plugin.getLogger().warning("The updater could not find any files for the project id " + this.id); + this.result = UpdateResult.FAIL_BADID; + return false; + } + + this.versionName = (String) ((JSONObject) array.get(array.size() - 1)).get(Updater.TITLE_VALUE); + this.versionLink = (String) ((JSONObject) array.get(array.size() - 1)).get(Updater.LINK_VALUE); + this.versionType = (String) ((JSONObject) array.get(array.size() - 1)).get(Updater.TYPE_VALUE); + this.versionGameVersion = (String) ((JSONObject) array.get(array.size() - 1)).get(Updater.VERSION_VALUE); + + return true; + } + catch (final IOException e) { + if (e.getMessage().contains("HTTP response code: 403")) { + this.plugin.getLogger().warning("dev.bukkit.org rejected the API key provided in plugins/Updater/config.yml"); + this.plugin.getLogger().warning("Please double-check your configuration to ensure it is correct."); + this.result = UpdateResult.FAIL_APIKEY; + } + else { + this.plugin.getLogger().warning("The updater could not contact dev.bukkit.org for updating."); + this.plugin.getLogger().warning("If you have not recently modified your configuration and this is the first time you are seeing this message, the site may be experiencing temporary downtime."); + this.result = UpdateResult.FAIL_DBO; + } + + if (Config.getInstance().getVerboseLoggingEnabled()) { + e.printStackTrace(); + } + + return false; + } + } + + private class UpdateRunnable implements Runnable { + + @Override + public void run() { + if (Updater.this.url != null) { + // Obtain the results of the project's file feed + if (Updater.this.read()) { + if (Updater.this.versionCheck(Updater.this.versionName)) { + if ((Updater.this.versionLink != null) && (Updater.this.type != UpdateType.NO_DOWNLOAD)) { + String name = Updater.this.file.getName(); + // If it's a zip file, it shouldn't be downloaded as the plugin's name + if (Updater.this.versionLink.endsWith(".zip")) { + final String[] split = Updater.this.versionLink.split("/"); + name = split[split.length - 1]; + } + Updater.this.saveFile(new File(Updater.this.plugin.getDataFolder().getParent(), Updater.this.updateFolder), name, Updater.this.versionLink); + } + else { + Updater.this.result = UpdateResult.UPDATE_AVAILABLE; + } + } + } + } + } + } +} diff --git a/src/main/java/plugin.yml b/src/main/java/plugin.yml new file mode 100644 index 0000000..7697da3 --- /dev/null +++ b/src/main/java/plugin.yml @@ -0,0 +1,56 @@ +name: Soulbound +version: 1.1.10-SNAPSHOT-b84 +author: TfT_02 +main: com.me.tft_02.soulbound.Soulbound +description: Soulbound items for your RPG servers! +softdepend: [EpicBossRecoded, LoreLocks, MythicDrops] +commands: + soulbound: + description: Usage /soulbound + permission-message: You don't have + bind: + description: Usage /bind + alias: bound + permission: soulbound.commands.bind + permission-message: You don't have + bindonpickup: + description: Usage /bindonpickup + permission: soulbound.commands.bindonpickup + permission-message: You don't have + bindonuse: + description: Usage /bindonuse + permission: soulbound.commands.bindonuse + permission-message: You don't have + bindonequip: + description: Usage /bindonequip + permission: soulbound.commands.bindonequip + permission-message: You don't have + unbind: + description: Usage /unbind + alias: unbound + permission: soulbound.commands.unbind + permission-message: You don't have +permissions: + soulbound.commands.all: + description: Gives access to the Soulbound commands + default: op + children: + soulbound.commands.bind: true + soulbound.commands.bindonpickup: true + soulbound.commands.bindonuse: true + soulbound.commands.bindonequip: true + soulbound.commands.unbind: true + soulbound.commands.reload: true + soulbound.updatecheck: true + soulbound.pickup.bypass: + description: Users with this permission will be able to pickup Soulbound items that do not belong to them. + default: false + soulbound.items.keep_on_death: + description: Users with this permission will keep their Soulbound items after dying + default: false + soulbound.items.delete_on_death: + description: Users with this permission will have their Soulbound items deleted on death + default: false + soulbound.updatecheck: + description: Users with this permission will receive a notification when there is a new version available. + default: op \ No newline at end of file