From 0d3e0847e9951a65425b5fc00306cdd01fe95c14 Mon Sep 17 00:00:00 2001 From: filoghost Date: Fri, 31 Oct 2014 09:09:43 +0100 Subject: [PATCH] First commit :D --- ChestCommands/menu/example.yml | 188 ++++++++++++ ChestCommands/placeholders.yml | 21 ++ ChestCommands/plugin.yml | 15 + .../chestcommands/ChestCommands.java | 270 ++++++++++++++++++ .../filoghost/chestcommands/Permissions.java | 15 + .../chestcommands/SimpleUpdater.java | 168 +++++++++++ .../chestcommands/api/ChestCommandsAPI.java | 36 +++ .../chestcommands/api/ClickHandler.java | 9 + .../filoghost/chestcommands/api/Icon.java | 213 ++++++++++++++ .../filoghost/chestcommands/api/IconMenu.java | 92 ++++++ .../chestcommands/bridge/BarAPIBridge.java | 7 + .../chestcommands/bridge/EconomyBridge.java | 80 ++++++ .../bridge/PlayerPointsBridge.java | 7 + .../bridge/bungee/BungeeCordUtils.java | 39 +++ .../command/CommandFramework.java | 151 ++++++++++ .../chestcommands/command/CommandHandler.java | 125 ++++++++ .../config/AsciiPlaceholders.java | 97 +++++++ .../filoghost/chestcommands/config/Lang.java | 18 ++ .../chestcommands/config/Settings.java | 17 ++ .../config/yaml/PluginConfig.java | 55 ++++ .../config/yaml/SpecialConfig.java | 116 ++++++++ .../exception/FormatException.java | 10 + .../chestcommands/internal/BoundItem.java | 53 ++++ .../internal/CommandsClickHandler.java | 29 ++ .../internal/ExtendedIconMenu.java | 54 ++++ .../chestcommands/internal/MenuData.java | 88 ++++++ .../internal/MenuInventoryHolder.java | 39 +++ .../chestcommands/internal/RequiredItem.java | 98 +++++++ .../chestcommands/internal/Variable.java | 55 ++++ .../internal/icon/ExtendedIcon.java | 113 ++++++++ .../internal/icon/IconCommand.java | 41 +++ .../internal/icon/StaticExtendedIcon.java | 25 ++ .../icon/command/BroadcastCommand.java | 21 ++ .../internal/icon/command/ConsoleCommand.java | 19 ++ .../internal/icon/command/GiveCommand.java | 38 +++ .../icon/command/GiveMoneyCommand.java | 40 +++ .../internal/icon/command/OpCommand.java | 26 ++ .../internal/icon/command/OpenCommand.java | 26 ++ .../internal/icon/command/PlayerCommand.java | 18 ++ .../internal/icon/command/ServerCommand.java | 19 ++ .../internal/icon/command/SoundCommand.java | 54 ++++ .../internal/icon/command/TellCommand.java | 19 ++ .../listener/CommandListener.java | 36 +++ .../listener/InventoryListener.java | 58 ++++ .../chestcommands/listener/JoinListener.java | 26 ++ .../chestcommands/listener/SignListener.java | 74 +++++ .../serializer/CommandSerializer.java | 98 +++++++ .../serializer/EnchantmentSerializer.java | 101 +++++++ .../serializer/IconSerializer.java | 197 +++++++++++++ .../serializer/MenuSerializer.java | 123 ++++++++ .../chestcommands/task/ErrorLoggerTask.java | 43 +++ .../util/CaseInsensitiveMap.java | 49 ++++ .../chestcommands/util/ClickType.java | 33 +++ .../chestcommands/util/ErrorLogger.java | 29 ++ .../chestcommands/util/InventoryUtils.java | 38 +++ .../chestcommands/util/ItemStackReader.java | 95 ++++++ .../chestcommands/util/StringUtils.java | 84 ++++++ .../filoghost/chestcommands/util/Utils.java | 261 +++++++++++++++++ .../chestcommands/util/Validate.java | 23 ++ 59 files changed, 3992 insertions(+) create mode 100644 ChestCommands/menu/example.yml create mode 100644 ChestCommands/placeholders.yml create mode 100644 ChestCommands/plugin.yml create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/ChestCommands.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/Permissions.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/SimpleUpdater.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/api/ChestCommandsAPI.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/api/ClickHandler.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/api/Icon.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/api/IconMenu.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/bridge/BarAPIBridge.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/bridge/EconomyBridge.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/bridge/PlayerPointsBridge.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/bridge/bungee/BungeeCordUtils.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/command/CommandFramework.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/command/CommandHandler.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/config/AsciiPlaceholders.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/config/Lang.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/config/Settings.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/config/yaml/PluginConfig.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/config/yaml/SpecialConfig.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/exception/FormatException.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/internal/BoundItem.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/internal/CommandsClickHandler.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/internal/ExtendedIconMenu.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/internal/MenuData.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/internal/MenuInventoryHolder.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/internal/RequiredItem.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/internal/Variable.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/ExtendedIcon.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/IconCommand.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/StaticExtendedIcon.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/BroadcastCommand.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/ConsoleCommand.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/GiveCommand.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/GiveMoneyCommand.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/OpCommand.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/OpenCommand.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/PlayerCommand.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/ServerCommand.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/SoundCommand.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/TellCommand.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/listener/CommandListener.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/listener/InventoryListener.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/listener/JoinListener.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/listener/SignListener.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/serializer/CommandSerializer.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/serializer/EnchantmentSerializer.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/serializer/IconSerializer.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/serializer/MenuSerializer.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/task/ErrorLoggerTask.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/util/CaseInsensitiveMap.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/util/ClickType.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/util/ErrorLogger.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/util/InventoryUtils.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/util/ItemStackReader.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/util/StringUtils.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/util/Utils.java create mode 100644 ChestCommands/src/com/gmail/filoghost/chestcommands/util/Validate.java diff --git a/ChestCommands/menu/example.yml b/ChestCommands/menu/example.yml new file mode 100644 index 0000000..f53713a --- /dev/null +++ b/ChestCommands/menu/example.yml @@ -0,0 +1,188 @@ +###################### +#+ +# +# MENU SETTINGS # +#+ +# +###################### + +menu-settings: + + # name - appears as the title - REQUIRED + name: '&1Example menu' + + # rows - the number of rows of the chest - REQUIRED + rows: 3 + + # command - OPTIONAL (you can remove this or set it to '') + # Bind multiple commands using ; (command: 'menu; m; me') + command: 'menu' + + # This command command will be execute when the menu is opened. + # Supports all the icon command types. + open-action: 'sound: note pling; tell: &eYou opened the example menu.' + + # open-with-item - OPTIONAL + # The menu will open only right-clicking with orange wool [35:1] + open-with-item: + id: wool:1 + left-click: false + right-click: true + + +###################### +#+ +# +# ITEMS # +#+ +# +###################### + + +spawncmd: + COMMAND: 'spawn' + NAME: '&e/spawn' + LORE: + - 'It justs executes /spawn' + - 'as the player who clicked.' + ID: bed + POSITION-X: 1 + POSITION-Y: 1 + + +colored-enchanted-stacked-wool: + NAME: '&aWool with additional data' + LORE: + - 'This wool has a data value, an amount' + - 'greater than 1, and two enchantments.' + ID: wool + DATA-VALUE: 1 + AMOUNT: 10 + ENCHANTMENT: knockback, 10; durability, 10 + POSITION-X: 2 + POSITION-Y: 1 + + +this-text-will-not-appear: + NAME: '&dFormatting codes' + LORE: + - 'You can use all the formatting codes!' + - '&fColors: &c#c &a#a &9#9 &e#e &f... ...' + - '&fRandom (#k): &kfy379!H' + - '&fBold (#l): &lexample' + - '&fStrikethrough (#m): &mexample' + - '&fUnderline (#n): &nexample' + - '&fItalic (#o): &oexample' + ID: paper + POSITION-X: 3 + POSITION-Y: 1 + + +test-from-console: + COMMAND: 'console: say Hello {player}!' + NAME: '&cRuns command from the console.' + LORE: + - 'It broadcasts your name with the command /say.' + ID: command block + POSITION-X: 4 + POSITION-Y: 1 + + +test-as-op: + COMMAND: 'op: say Hello world!' + NAME: '&cRuns command as OP.' + LORE: + - 'Be careful with this type of commands.' + - 'It will ignore nearly all the permissions.' + ID: command block + POSITION-X: 5 + POSITION-Y: 1 + + +test-with-placeholder: + COMMAND: 'tell: &9Online players: &f{online}/{max_players}; tell: &9Your name: &f{player}; tell: &9The world you are in: &f{world}; tell: &9Money: &f{money}' + NAME: '&6This message contains some placeholders' + LORE: + - 'Placeholders will be replaced when the item' + - 'is clicked.' + ID: empty map + POSITION-X: 6 + POSITION-Y: 1 + + +economy-give: + COMMAND: 'tell: &aYou have paid 50$ for this command!; give: gold_ingot' + NAME: '&eEconomy & Give command' + LORE: + - 'This command will be executed' + - 'only if you have at least 50$!' + - 'It gives you a gold ingot.' + ID: gold ingot + PRICE: 50 + POSITION-X: 7 + POSITION-Y: 1 + + +economy-take: + REQUIRED-ITEM: 'gold_ingot' + COMMAND: 'tell: &aYou have been paid 50$; givemoney: 50' + NAME: '&eEconomy & Required item' + LORE: + - 'This command is the opposite of the previous.' + - 'It will take you a gold ingot and give you $50.' + ID: gold ingot + POSITION-X: 8 + POSITION-Y: 1 + + +will-not-close: + NAME: '&2Info' + LORE: + - '&7The menu will not close' + - '&7clicking on this item.' + ID: sign + KEEP-OPEN: true + POSITION-X: 9 + POSITION-Y: 1 + + +a-talking-head: + COMMAND: 'tell: This is a simple message, without using commands!' + NAME: '&3Tells you something.' + LORE: + - '&7It tells you something without commands.' + ID: head + DATA-VALUE: 3 + POSITION-X: 1 + POSITION-Y: 2 + + +test-multiple-command: + COMMAND: 'console: Say Did you know that...; console: say you can run multiple commands?;' + NAME: '&aMultiple commands' + LORE: + - 'Example of multiple commands.' + ID: golden apple + POSITION-X: 2 + POSITION-Y: 2 + + +permission: + COMMAND: 'tell: &a[v] You have the correct permission!' + NAME: '&8Permission test' + LORE: + - 'To use this item, you need the' + - 'permission "chestcommands.test".' + - 'Otherwise, a configurable error' + - 'message will be displayed.' + ID: iron bar + POSITION-X: 3 + POSITION-Y: 2 + PERMISSION: chestcommands.test + PERMISSION-MESSAGE: 'You don''t have the correct permission!' + + +menu-close-no-commands-no-lore: + NAME: '&6Close the menu' + ID: redstone lamp + LORE: + - 'Create an item without command,' + - 'the GUI will just close.' + POSITION-X: 9 + POSITION-Y: 3 \ No newline at end of file diff --git a/ChestCommands/placeholders.yml b/ChestCommands/placeholders.yml new file mode 100644 index 0000000..839ce68 --- /dev/null +++ b/ChestCommands/placeholders.yml @@ -0,0 +1,21 @@ +# +# This is the configuration file for static placeholders. +# Dynamic placeholders are {online}, {max_players}, ... +# Static placeholders are symbols. +# +# List of unicode symbols: http://www.fileformat.info/info/unicode/index.htm +# +<3: \u2764 +[*]: \u2605 +[**]: \u2739 +[p]: \u2022 +[v]: \u2714 +[+]: \u25C6 +[++]: \u2726 +[x]: \u2588 +[/]: \u258C +[cross]: \u2720 +[arrow_right]: \u27A1 +[arrow_left]: \u2B05 +[arrow_up]: \u2B06 +[arrow_down]:\u2B07 \ No newline at end of file diff --git a/ChestCommands/plugin.yml b/ChestCommands/plugin.yml new file mode 100644 index 0000000..03c0752 --- /dev/null +++ b/ChestCommands/plugin.yml @@ -0,0 +1,15 @@ +name: ChestCommands +main: com.gmail.filoghost.chestcommands.ChestCommands +version: 3.0 +softdepend: [Vault] + +commands: + chestcommands: + description: Main command for ChestCommands. + usage: / (Startup error) + aliases: [cc] + +permissions: + chestcommands.economy.bypass: + description: Bypass command costs. + default: false \ No newline at end of file diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/ChestCommands.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/ChestCommands.java new file mode 100644 index 0000000..a3b8c04 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/ChestCommands.java @@ -0,0 +1,270 @@ +package com.gmail.filoghost.chestcommands; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.plugin.java.JavaPlugin; +import org.mcstats.MetricsLite; + +import com.gmail.filoghost.chestcommands.SimpleUpdater.ResponseHandler; +import com.gmail.filoghost.chestcommands.bridge.EconomyBridge; +import com.gmail.filoghost.chestcommands.command.CommandFramework; +import com.gmail.filoghost.chestcommands.command.CommandHandler; +import com.gmail.filoghost.chestcommands.config.AsciiPlaceholders; +import com.gmail.filoghost.chestcommands.config.Lang; +import com.gmail.filoghost.chestcommands.config.Settings; +import com.gmail.filoghost.chestcommands.config.yaml.PluginConfig; +import com.gmail.filoghost.chestcommands.internal.BoundItem; +import com.gmail.filoghost.chestcommands.internal.ExtendedIconMenu; +import com.gmail.filoghost.chestcommands.internal.MenuData; +import com.gmail.filoghost.chestcommands.listener.CommandListener; +import com.gmail.filoghost.chestcommands.listener.InventoryListener; +import com.gmail.filoghost.chestcommands.listener.JoinListener; +import com.gmail.filoghost.chestcommands.listener.SignListener; +import com.gmail.filoghost.chestcommands.serializer.CommandSerializer; +import com.gmail.filoghost.chestcommands.serializer.MenuSerializer; +import com.gmail.filoghost.chestcommands.task.ErrorLoggerTask; +import com.gmail.filoghost.chestcommands.util.CaseInsensitiveMap; +import com.gmail.filoghost.chestcommands.util.ErrorLogger; +import com.gmail.filoghost.chestcommands.util.Utils; +import com.google.common.collect.Sets; + +public class ChestCommands extends JavaPlugin { + + public static final String CHAT_PREFIX = ChatColor.DARK_GREEN + "[" + ChatColor.GREEN + "ChestCommands" + ChatColor.DARK_GREEN + "] " + ChatColor.GREEN; + + private static ChestCommands instance; + private static Settings settings; + private static Lang lang; + + private static Map fileNameToMenuMap; + private static Map commandsToMenuMap; + + private static Set boundItems; + + private static int lastReloadErrors; + private static String newVersion; + + @Override + public void onEnable() { + if (instance != null) { + getLogger().warning("Please do not use /reload or plugin reloaders. Do \"/cc reload\" instead."); + return; + } + + instance = this; + fileNameToMenuMap = CaseInsensitiveMap.create(); + commandsToMenuMap = CaseInsensitiveMap.create(); + boundItems = Sets.newHashSet(); + + settings = new Settings(new PluginConfig(this, "config.yml")); + lang = new Lang(new PluginConfig(this, "lang.yml")); + + if (!EconomyBridge.setupEconomy()) { + getLogger().info("Vault with a compatible economy plugin was not found! Icons with a PRICE or commands that give money will not work."); + } + + if (settings.update_notifications) { + new SimpleUpdater(this, 56919).checkForUpdates(new ResponseHandler() { + + @Override + public void onUpdateFound(String newVersion) { + ChestCommands.newVersion = newVersion; + + if (settings.use_console_colors) { + Bukkit.getConsoleSender().sendMessage(CHAT_PREFIX + "Found a new version: " + newVersion + ChatColor.WHITE + " (yours: v" + getDescription().getVersion() + ")"); + Bukkit.getConsoleSender().sendMessage(CHAT_PREFIX + ChatColor.WHITE + "Download it on Bukkit Dev:"); + Bukkit.getConsoleSender().sendMessage(CHAT_PREFIX + ChatColor.WHITE + "dev.bukkit.org/bukkit-plugins/chest-commands"); + } else { + getLogger().info("Found a new version available: " + newVersion); + getLogger().info("Download it on Bukkit Dev:"); + getLogger().info("dev.bukkit.org/bukkit-plugins/chest-commands"); + } + } + }); + } + + try { + new MetricsLite(this).start(); + } catch (IOException e) { + // Metrics failed. + } + + Bukkit.getPluginManager().registerEvents(new CommandListener(), this); + Bukkit.getPluginManager().registerEvents(new InventoryListener(), this); + Bukkit.getPluginManager().registerEvents(new JoinListener(), this); + Bukkit.getPluginManager().registerEvents(new SignListener(), this); + + CommandFramework.register(this, new CommandHandler("chestcommands")); + + ErrorLogger errorLogger = new ErrorLogger(); + load(errorLogger); + + lastReloadErrors = errorLogger.getSize(); + if (errorLogger.hasErrors()) { + Bukkit.getScheduler().scheduleSyncDelayedTask(this, new ErrorLoggerTask(errorLogger), 10L); + } + } + + public void load(ErrorLogger errorLogger) { + fileNameToMenuMap.clear(); + commandsToMenuMap.clear(); + boundItems.clear(); + + CommandSerializer.checkClassConstructors(errorLogger); + + try { + settings.load(); + } catch (IOException e) { + e.printStackTrace(); + getLogger().warning("I/O error while using the configuration. Default values will be used."); + } catch (InvalidConfigurationException e) { + e.printStackTrace(); + getLogger().warning("The config.yml was not valid, please look at the error above. Default values will be used."); + } catch (Exception e) { + e.printStackTrace(); + getLogger().warning("Unhandled error while reading the values for the configuration! Please inform the developer."); + } + + try { + lang.load(); + } catch (IOException e) { + e.printStackTrace(); + getLogger().warning("I/O error while using the language file. Default values will be used."); + } catch (InvalidConfigurationException e) { + e.printStackTrace(); + getLogger().warning("The lang.yml was not valid, please look at the error above. Default values will be used."); + } catch (Exception e) { + e.printStackTrace(); + getLogger().warning("Unhandled error while reading the values for the configuration! Please inform the developer."); + } + + try { + AsciiPlaceholders.load(errorLogger); + } catch (IOException e) { + e.printStackTrace(); + getLogger().warning("I/O error while reading the placeholders. They will not work."); + } catch (Exception e) { + e.printStackTrace(); + getLogger().warning("Unhandled error while reading the placeholders! Please inform the developer."); + } + + // Load the menus. + List menusList = new ArrayList(); + File menusFolder = new File(getDataFolder(), "menu"); + + if (!menusFolder.isDirectory()) { + // Create the directory with the default menu. + menusFolder.mkdirs(); + Utils.saveResourceSafe(this, "menu" + File.separator + "example.yml"); + } + + loadMenus(menusList, menusFolder); + for (PluginConfig menuConfig : menusList) { + try { + menuConfig.load(); + } catch (IOException e) { + e.printStackTrace(); + errorLogger.addError("I/O error while loading the menu \"" + menuConfig.getFileName() + "\". Is the file in use?"); + continue; + } catch (InvalidConfigurationException e) { + e.printStackTrace(); + errorLogger.addError("Invalid YAML configuration for the menu \"" + menuConfig.getFileName() + "\". Please look at the error above, or use an online YAML parser (google is your friend)."); + continue; + } + + MenuData data = MenuSerializer.loadMenuData(menuConfig, errorLogger); + ExtendedIconMenu iconMenu = MenuSerializer.loadMenu(menuConfig, data.getTitle(), data.getRows(), errorLogger); + + fileNameToMenuMap.put(menuConfig.getFileName(), iconMenu); + + if (data.hasCommands()) { + for (String command : data.getCommands()) { + if (!command.isEmpty()) { + commandsToMenuMap.put(command, iconMenu); + } + } + } + + if (data.getOpenActions() != null) { + iconMenu.setOpenActions(data.getOpenActions()); + } + + if (data.hasBoundMaterial() && data.getClickType() != null) { + BoundItem boundItem = new BoundItem(iconMenu, data.getBoundMaterial(), data.getClickType()); + if (data.hasBoundDataValue()) { + boundItem.setRestrictiveData(data.getBoundDataValue()); + } + boundItems.add(boundItem); + } + } + + // Register the BungeeCord plugin channel. + if (!Bukkit.getMessenger().isOutgoingChannelRegistered(this, "BungeeCord")) { + Bukkit.getMessenger().registerOutgoingPluginChannel(this, "BungeeCord"); + } + } + + /** + * Loads all the configuration files recursively into a list. + */ + private void loadMenus(List list, File file) { + if (file.isDirectory()) { + for (File subFile : file.listFiles()) { + loadMenus(list, subFile); + } + } else if (file.isFile()) { + if (file.getName().endsWith(".yml")) { + list.add(new PluginConfig(this, file)); + } + } + } + + public static ChestCommands getInstance() { + return instance; + } + + public static Settings getSettings() { + return settings; + } + + public static Lang getLang() { + return lang; + } + + public static boolean hasNewVersion() { + return newVersion != null; + } + + public static String getNewVersion() { + return newVersion; + } + + public static Map getFileNameToMenuMap() { + return fileNameToMenuMap; + } + + public static Map getCommandToMenuMap() { + return commandsToMenuMap; + } + + public static Set getBoundItems() { + return boundItems; + } + + public static int getLastReloadErrors() { + return lastReloadErrors; + } + + public static void setLastReloadErrors(int lastReloadErrors) { + ChestCommands.lastReloadErrors = lastReloadErrors; + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/Permissions.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/Permissions.java new file mode 100644 index 0000000..7de8b21 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/Permissions.java @@ -0,0 +1,15 @@ +package com.gmail.filoghost.chestcommands; + +public class Permissions { + + public static final String + + UPDATE_NOTIFICATIONS = "chestcommands.update", + SEE_ERRORS = "chestcommands.errors", + SIGN_CREATE = "chestcommands.sign", + BYPASS_ECONOMY = "chestcommands.economy.bypass", + + COMMAND_BASE = "chestcommands.command.", + OPEN_MENU_BASE = "chestcommands.open."; + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/SimpleUpdater.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/SimpleUpdater.java new file mode 100644 index 0000000..ab0b240 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/SimpleUpdater.java @@ -0,0 +1,168 @@ +package com.gmail.filoghost.chestcommands; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.JSONValue; + +/** + * A very simple and lightweight updater. + */ +public final class SimpleUpdater { + + public interface ResponseHandler { + + /** + * Called when the updater finds a new version. + * @param newVersion - the new version + */ + public void onUpdateFound(final String newVersion); + + } + + private Plugin plugin; + private int projectId; + + public SimpleUpdater(Plugin plugin, int projectId) { + if (plugin == null) { + throw new NullPointerException("Plugin cannot be null"); + } + + this.plugin = plugin; + this.projectId = projectId; + } + + /** + * This method creates a new async thread to check for updates. + */ + public void checkForUpdates(final ResponseHandler responseHandler) { + Thread updaterThread = new Thread(new Runnable() { + @Override + public void run() { + + try { + JSONArray filesArray = (JSONArray) readJson("https://api.curseforge.com/servermods/files?projectIds=" + projectId); + + if (filesArray.size() == 0) { + // The array cannot be empty, there must be at least one file. The project ID is not valid. + plugin.getLogger().warning("The author of this plugin has misconfigured the Updater system."); + plugin.getLogger().warning("The project ID (" + projectId + ") provided for updating is invalid."); + plugin.getLogger().warning("Please notify the author of this error."); + return; + } + + String updateName = (String) ((JSONObject) filesArray.get(filesArray.size() - 1)).get("name"); + final String newVersion = extractVersion(updateName); + + if (newVersion == null) { + throw new NumberFormatException(); + } + + if (isNewerVersion(newVersion)) { + Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() { + + public void run() { + responseHandler.onUpdateFound(newVersion); + } + }); + } + + } catch (IOException e) { + plugin.getLogger().warning("Could not contact BukkitDev to check for updates."); + } catch (NumberFormatException e) { + plugin.getLogger().warning("The author of this plugin has misconfigured the Updater system."); + plugin.getLogger().warning("File versions should follow the format 'PluginName vVERSION'"); + plugin.getLogger().warning("Please notify the author of this error."); + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + plugin.getLogger().warning("Unable to check for updates: unhandled exception."); + } + + } + }); + updaterThread.start(); + } + + private Object readJson(String url) throws MalformedURLException, IOException { + + URLConnection conn = new URL(url).openConnection(); + conn.setConnectTimeout(5000); + conn.setReadTimeout(8000); + conn.addRequestProperty("User-Agent", "Updater (by filoghost)"); + conn.setDoOutput(true); + + return JSONValue.parse(new BufferedReader(new InputStreamReader(conn.getInputStream()))); + } + + /** + * Compare the version found with the plugin's version, from an array of integer separated by full stops. + * Examples: + * v1.2 > v1.12 + * v2.1 = v2.01 + */ + private boolean isNewerVersion(String remoteVersion) { + String pluginVersion = plugin.getDescription().getVersion(); + + if (pluginVersion == null) { + // Do not throw exceptions, just consider it as v0. + pluginVersion = "0"; + } + + if (!remoteVersion.matches("v?[0-9\\.]+")) { + // Should always be checked before by this class. + throw new IllegalArgumentException("fetched version's format is incorrect"); + } + + // Remove all the "v" from the versions, replace multiple full stops with a single full stop, and split them. + String[] pluginVersionSplit = pluginVersion.replace("v", "").replaceAll("[\\.]{2,}", ".").split("\\."); + String[] remoteVersionSplit = remoteVersion.replace("v", "").replaceAll("[\\.]{2,}", ".").split("\\."); + + int longest = Math.max(pluginVersionSplit.length, remoteVersionSplit.length); + + int[] pluginVersionArray = new int[longest]; + int[] remoteVersionArray = new int[longest]; + + for (int i = 0; i < pluginVersionSplit.length; i++) { + pluginVersionArray[i] = Integer.parseInt(pluginVersionSplit[i]); + } + + for (int i = 0; i < remoteVersionSplit.length; i++) { + remoteVersionArray[i] = Integer.parseInt(remoteVersionSplit[i]); + } + + for (int i = 0; i < longest; i++) { + int diff = remoteVersionArray[i] - pluginVersionArray[i]; + if (diff > 0) { + return true; + } else if (diff < 0) { + return false; + } + + // Continue the loop. + } + + return false; + } + + private String extractVersion(String input) { + Matcher matcher = Pattern.compile("v[0-9\\.]+").matcher(input); + + String result = null; + if (matcher.find()) { + result = matcher.group(); + } + + return result; + } +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/api/ChestCommandsAPI.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/api/ChestCommandsAPI.java new file mode 100644 index 0000000..dc5eb39 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/api/ChestCommandsAPI.java @@ -0,0 +1,36 @@ +package com.gmail.filoghost.chestcommands.api; + +import org.bukkit.entity.Player; + +import com.gmail.filoghost.chestcommands.ChestCommands; + +public class ChestCommandsAPI { + + /** + * Checks if a name with a given file name was loaded by the plugin. + * + * @return true - if the menu was found. + */ + public static boolean isPluginMenu(String yamlFile) { + return ChestCommands.getFileNameToMenuMap().containsKey(yamlFile); + } + + /** + * Opens a menu loaded by ChestCommands to a player. + * NOTE: this method ignores permissions. + * + * @param player - the player that will see the GUI. + * @param yamlFile - the file name of the menu to open. The .yml extension CANNOT be omitted. + * @return true - if the menu was found and opened, false if not. + */ + public static boolean openPluginMenu(Player player, String yamlFile) { + IconMenu menu = ChestCommands.getFileNameToMenuMap().get(yamlFile); + + if (menu != null) { + menu.open(player); + return true; + } else { + return false; + } + } +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/api/ClickHandler.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/api/ClickHandler.java new file mode 100644 index 0000000..03398bb --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/api/ClickHandler.java @@ -0,0 +1,9 @@ +package com.gmail.filoghost.chestcommands.api; + +import org.bukkit.entity.Player; + +public interface ClickHandler { + + public void onClick(Player player); + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/api/Icon.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/api/Icon.java new file mode 100644 index 0000000..cf8c2f2 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/api/Icon.java @@ -0,0 +1,213 @@ +package com.gmail.filoghost.chestcommands.api; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.bukkit.ChatColor; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.LeatherArmorMeta; + +import com.google.common.collect.Lists; + +public class Icon { + + private Material material; + private int amount; + private short dataValue; + + private String name; + private List lore; + private Map enchantments; + private Color color; + + private boolean closeOnClick; + private ClickHandler clickHandler; + + public Icon() { + closeOnClick = true; + enchantments = new HashMap(); + } + + public void setMaterial(Material material) { + if (material == Material.AIR) material = null; + this.material = material; + } + + public Material getMaterial() { + return material; + } + + public void setAmount(int amount) { + if (amount < 1) amount = 1; + else if (amount > 127) amount = 127; + + this.amount = amount; + } + + public int getAmount() { + return amount; + } + + public void setDataValue(short dataValue) { + if (dataValue < 0) dataValue = 0; + + this.dataValue = dataValue; + } + + public short getDataValue() { + return dataValue; + } + + public void setName(String name) { + this.name = name; + } + + public boolean hasName() { + return name != null; + } + + public void setLore(String... lore) { + if (lore != null) { + this.lore = Arrays.asList(lore); + } + } + + public void setLore(List lore) { + this.lore = lore; + } + + public boolean hasLore() { + return lore != null && lore.size() > 0; + } + + public List getLore() { + return lore; + } + + public void setEnchantments(Map enchantments) { + if (enchantments == null) { + clearEnchantments(); + return; + } + this.enchantments = enchantments; + } + + public Map getEnchantments() { + return new HashMap(enchantments); + } + + public void addEnchantment(Enchantment ench) { + addEnchantment(ench, 1); + } + + public void addEnchantment(Enchantment ench, Integer level) { + enchantments.put(ench, level); + } + + public void removeEnchantment(Enchantment ench) { + enchantments.remove(ench); + } + + public void clearEnchantments() { + enchantments.clear(); + } + + public Color getColor() { + return color; + } + + public void setColor(Color color) { + this.color = color; + } + + public void setCloseOnClick(boolean closeOnClick) { + this.closeOnClick = closeOnClick; + } + + public boolean isCloseOnClick() { + return closeOnClick; + } + + public void setClickHandler(ClickHandler clickHandler) { + this.clickHandler = clickHandler; + } + + public ClickHandler getClickHandler() { + return clickHandler; + } + + protected String calculateName() { + if (hasName()) { + // TODO some magic + return name; + } + + return null; + } + + protected List calculateLore() { + + List output = null; + + if (hasLore()) { + + output = Lists.newArrayList(); + // TODO some magic + for (String line : lore) { + output.add(line); + } + } + + if (material == null) { + + if (output == null) output = Lists.newArrayList(); + + // Add an error message. + output.add(ChatColor.RED + "(Invalid material)"); + } + + return output; + } + + public ItemStack createItemstack() { + + // If the material is not set, display BEDROCK. + ItemStack itemStack = (material != null) ? new ItemStack(material, amount, dataValue) : new ItemStack(Material.BEDROCK, amount); + + // Apply name, lore and color. + ItemMeta itemMeta = itemStack.getItemMeta(); + + itemMeta.setDisplayName(calculateName()); + itemMeta.setLore(calculateLore()); + + if (color != null && itemMeta instanceof LeatherArmorMeta) { + ((LeatherArmorMeta) itemMeta).setColor(color); + } + + itemStack.setItemMeta(itemMeta); + + // Apply enchants. + if (enchantments.size() > 0) { + for (Entry entry : enchantments.entrySet()) { + itemStack.addUnsafeEnchantment(entry.getKey(), entry.getValue()); + } + } + + return itemStack; + } + + public void onClick(Player whoClicked) { + if (clickHandler != null) { + clickHandler.onClick(whoClicked); + } + + } +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/api/IconMenu.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/api/IconMenu.java new file mode 100644 index 0000000..6bead20 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/api/IconMenu.java @@ -0,0 +1,92 @@ +package com.gmail.filoghost.chestcommands.api; + +import java.util.Arrays; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; + +import com.gmail.filoghost.chestcommands.internal.MenuInventoryHolder; +import com.gmail.filoghost.chestcommands.util.Utils; +import com.gmail.filoghost.chestcommands.util.Validate; + +/* + * MEMO: Raw slot numbers + * + * | 0| 1| 2| 3| 4| 5| 6| 7| 8| + * | 9|10|11|12|13|14|15|16|17| + * ... + * + */ +public class IconMenu { + + private String title; + private Icon[] icons; + + + public IconMenu(String title, int rows) { + this.title = title; + icons = new Icon[rows * 9]; + } + + public void setIcon(int x, int y, Icon icon) { + int slot = Utils.makePositive(y - 1) * 9 + Utils.makePositive(x - 1); + if (slot >= 0 && slot < icons.length) { + icons[slot] = icon; + } + } + + public void setIconRaw(int slot, Icon icon) { + if (slot >= 0 && slot < icons.length) { + icons[slot] = icon; + } + } + + public Icon getIcon(int x, int y) { + int slot = Utils.makePositive(y - 1) * 9 + Utils.makePositive(x - 1); + if (slot >= 0 && slot < icons.length) { + return icons[slot]; + } + + return null; + } + + public Icon getIconRaw(int slot) { + if (slot >= 0 && slot < icons.length) { + return icons[slot]; + } + + return null; + } + + public int getRows() { + return icons.length / 9; + } + + public int getSize() { + return icons.length; + } + + public String getTitle() { + return title; + } + + public void open(Player player) { + Validate.notNull(player, "Player cannot be null"); + + Inventory inventory = Bukkit.createInventory(new MenuInventoryHolder(this), icons.length, title); + + for (int i = 0; i < icons.length; i++) { + if (icons[i] != null) { + inventory.setItem(i, icons[i].createItemstack()); + } + } + + player.openInventory(inventory); + } + + @Override + public String toString() { + return "IconMenu [title=" + title + ", icons=" + Arrays.toString(icons) + "]"; + } +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/bridge/BarAPIBridge.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/bridge/BarAPIBridge.java new file mode 100644 index 0000000..3440b91 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/bridge/BarAPIBridge.java @@ -0,0 +1,7 @@ +package com.gmail.filoghost.chestcommands.bridge; + +public class BarAPIBridge { + + // A big TODO + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/bridge/EconomyBridge.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/bridge/EconomyBridge.java new file mode 100644 index 0000000..a52990f --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/bridge/EconomyBridge.java @@ -0,0 +1,80 @@ +package com.gmail.filoghost.chestcommands.bridge; + +import net.milkbowl.vault.economy.Economy; +import net.milkbowl.vault.economy.EconomyResponse; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.plugin.RegisteredServiceProvider; + +public class EconomyBridge { + + private static Economy economy; + + public static boolean setupEconomy() { + if (Bukkit.getPluginManager().getPlugin("Vault") == null) { + return false; + } + RegisteredServiceProvider rsp = Bukkit.getServicesManager().getRegistration(Economy.class); + if (rsp == null) { + return false; + } + economy = rsp.getProvider(); + return economy != null; + } + + public static boolean hasValidEconomy() { + return economy != null; + } + + public static Economy getEconomy() { + if (!hasValidEconomy()) throw new IllegalStateException("Economy plugin was not found!"); + return economy; + } + + public static double getMoney(Player player) { + if (!hasValidEconomy()) throw new IllegalStateException("Economy plugin was not found!"); + return economy.getBalance(player.getName(), player.getWorld().getName()); + } + + public static boolean hasMoney(Player player, double minimum) { + if (!hasValidEconomy()) throw new IllegalStateException("Economy plugin was not found!"); + if (minimum < 0.0) throw new IllegalArgumentException("Invalid amount of money: " + minimum); + + double balance = economy.getBalance(player.getName(), player.getWorld().getName()); + + if (balance < minimum) { + return false; + } else { + return true; + } + } + + /** + * @return true if the operation was successful. + */ + public static boolean takeMoney(Player player, double amount) { + if (!hasValidEconomy()) throw new IllegalStateException("Economy plugin was not found!"); + if (amount < 0.0) throw new IllegalArgumentException("Invalid amount of money: " + amount); + + EconomyResponse response = economy.withdrawPlayer(player.getName(), player.getWorld().getName(), amount); + return response.transactionSuccess(); + } + + + public static boolean giveMoney(Player player, double amount) { + if (!hasValidEconomy()) throw new IllegalStateException("Economy plugin was not found!"); + if (amount < 0.0) throw new IllegalArgumentException("Invalid amount of money: " + amount); + + EconomyResponse response = economy.depositPlayer(player.getName(), player.getWorld().getName(), amount); + return response.transactionSuccess(); + } + + public static String formatMoney(double amount) { + if (hasValidEconomy()) { + return economy.format(amount); + } else { + return Double.toString(amount); + } + } +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/bridge/PlayerPointsBridge.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/bridge/PlayerPointsBridge.java new file mode 100644 index 0000000..c83d8a0 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/bridge/PlayerPointsBridge.java @@ -0,0 +1,7 @@ +package com.gmail.filoghost.chestcommands.bridge; + +public class PlayerPointsBridge { + + // A big TODO + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/bridge/bungee/BungeeCordUtils.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/bridge/bungee/BungeeCordUtils.java new file mode 100644 index 0000000..02a0eab --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/bridge/bungee/BungeeCordUtils.java @@ -0,0 +1,39 @@ +package com.gmail.filoghost.chestcommands.bridge.bungee; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; + +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import com.gmail.filoghost.chestcommands.ChestCommands; + +public class BungeeCordUtils { + + public static boolean connect(Player player, String server) { + + try { + + if (server.length() == 0) { + player.sendMessage("§cTarget server was \"\" (empty string) cannot connect to it."); + return false; + } + + ByteArrayOutputStream byteArray = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(byteArray); + + out.writeUTF("Connect"); + out.writeUTF(server); // Target Server. + + player.sendPluginMessage(ChestCommands.getInstance(), "BungeeCord", byteArray.toByteArray()); + + } catch (Exception ex) { + player.sendMessage(ChatColor.RED + "An unexpected exception has occurred. Please notify the server's staff about this. (They should look at the console)."); + ex.printStackTrace(); + ChestCommands.getInstance().getLogger().warning("Could not connect \"" + player.getName() + "\" to the server \"" + server + "\"."); + return false; + } + + return true; + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/command/CommandFramework.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/command/CommandFramework.java new file mode 100644 index 0000000..d746314 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/command/CommandFramework.java @@ -0,0 +1,151 @@ +package com.gmail.filoghost.chestcommands.command; + +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.PluginCommand; +import org.bukkit.plugin.java.JavaPlugin; + +/** + * Wrapper for the default command executor. + */ +public abstract class CommandFramework implements CommandExecutor { + + /*************************************************** + * + * STATIC REGISTER METHOD + * + ***************************************************/ + public static boolean register(JavaPlugin plugin, CommandFramework command) { + PluginCommand pluginCommand = plugin.getCommand(command.label); + + if (pluginCommand == null) { + return false; + } + + pluginCommand.setExecutor(command); + return true; + } + + /*************************************************** + * + * COMMAND FRAMEWORK CLASS + * + ***************************************************/ + private String label; + + public CommandFramework(String label) { + this.label = label; + } + + @Override + public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { + try { + + execute(sender, label, args); + + } catch (CommandException ex) { + + if (ex.getMessage() != null && !ex.getMessage().isEmpty()) { + // Use RED by default. + sender.sendMessage(ChatColor.RED + ex.getMessage()); + } + } + + return true; + } + + public abstract void execute(CommandSender sender, String label, String[] args); + + + /*************************************************** + * + * COMMAND EXCEPTION + * + ***************************************************/ + public static class CommandException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public CommandException(String msg) { + super(msg); + } + + } + + + /*************************************************** + * + * VALIDATE CLASS + * + ***************************************************/ + public static class CommandValidate { + + public static void notNull(Object o, String msg) { + if (o == null) { + throw new CommandException(msg); + } + } + + public static void isTrue(boolean b, String msg) { + if (!b) { + throw new CommandException(msg); + } + } + + public static int getPositiveInteger(String input) { + try { + int i = Integer.parseInt(input); + if (i < 0) { + throw new CommandException("The number must be 0 or positive."); + } + return i; + } catch (NumberFormatException e) { + throw new CommandException("Invalid number \"" + input + "\"."); + } + } + + public static int getPositiveIntegerNotZero(String input) { + try { + int i = Integer.parseInt(input); + if (i <= 0) { + throw new CommandException("The number must be positive."); + } + return i; + } catch (NumberFormatException e) { + throw new CommandException("Invalid number \"" + input + "\"."); + } + } + + public static double getPositiveDouble(String input) { + try { + double d = Double.parseDouble(input); + if (d < 0) { + throw new CommandException("The number must be 0 or positive."); + } + return d; + } catch (NumberFormatException e) { + throw new CommandException("Invalid number \"" + input + "\"."); + } + } + + public static double getPositiveDoubleNotZero(String input) { + try { + double d = Integer.parseInt(input); + if (d <= 0) { + throw new CommandException("The number must be positive."); + } + return d; + } catch (NumberFormatException e) { + throw new CommandException("Invalid number \"" + input + "\"."); + } + } + + public static void minLength(Object[] array, int minLength, String msg) { + if (array.length < minLength) { + throw new CommandException(msg); + } + } + } +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/command/CommandHandler.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/command/CommandHandler.java new file mode 100644 index 0000000..91b2ef0 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/command/CommandHandler.java @@ -0,0 +1,125 @@ +package com.gmail.filoghost.chestcommands.command; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.Player; + +import com.gmail.filoghost.chestcommands.ChestCommands; +import com.gmail.filoghost.chestcommands.Permissions; +import com.gmail.filoghost.chestcommands.api.IconMenu; +import com.gmail.filoghost.chestcommands.internal.MenuInventoryHolder; +import com.gmail.filoghost.chestcommands.task.ErrorLoggerTask; +import com.gmail.filoghost.chestcommands.util.ErrorLogger; + +public class CommandHandler extends CommandFramework { + + public CommandHandler(String label) { + super(label); + } + + @Override + public void execute(CommandSender sender, String label, String[] args) { + if (args.length == 0) { + // This info is accessible to anyone. Please don't remove it, remember that Chest Commands is developed for FREE. + sender.sendMessage(ChestCommands.CHAT_PREFIX); + sender.sendMessage(ChatColor.GREEN + "Version: " + ChatColor.GRAY + ChestCommands.getInstance().getDescription().getVersion()); + sender.sendMessage(ChatColor.GREEN + "Developer: " + ChatColor.GRAY + "filoghost"); + sender.sendMessage(ChatColor.GREEN + "Commands: " + ChatColor.GRAY + "/" + label + " help"); + return; + } + + + + if (args[0].equalsIgnoreCase("help")) { + CommandValidate.isTrue(sender.hasPermission(Permissions.COMMAND_BASE + "help"), "You don't have permission."); + sender.sendMessage(ChestCommands.CHAT_PREFIX + " Commands:"); + sender.sendMessage(ChatColor.WHITE + "/" + label + " reload" + ChatColor.GRAY + " - Reloads the plugin."); + sender.sendMessage(ChatColor.WHITE + "/" + label + " list" + ChatColor.GRAY + " - Lists the loaded menus."); + sender.sendMessage(ChatColor.WHITE + "/" + label + " open [player]" + ChatColor.GRAY + " - Opens a menu for a player."); + return; + } + + + + if (args[0].equalsIgnoreCase("reload")) { + CommandValidate.isTrue(sender.hasPermission(Permissions.COMMAND_BASE + "reload"), "You don't have permission."); + + for (Player player : Bukkit.getOnlinePlayers()) { + if (player.getOpenInventory() != null) { + if (player.getOpenInventory().getTopInventory().getHolder() instanceof MenuInventoryHolder || player.getOpenInventory().getBottomInventory().getHolder() instanceof MenuInventoryHolder) { + player.closeInventory(); + } + } + } + + ErrorLogger errorLogger = new ErrorLogger(); + ChestCommands.getInstance().load(errorLogger); + + ChestCommands.setLastReloadErrors(errorLogger.getSize()); + + if (!errorLogger.hasErrors()) { + sender.sendMessage(ChestCommands.CHAT_PREFIX + "Plugin reloaded."); + } else { + new ErrorLoggerTask(errorLogger).run(); + sender.sendMessage(ChestCommands.CHAT_PREFIX + ChatColor.RED + "Plugin reloaded with " + errorLogger.getSize() + " error(s)."); + if (!(sender instanceof ConsoleCommandSender)) { + sender.sendMessage(ChestCommands.CHAT_PREFIX + ChatColor.RED + "Please check the console."); + } + } + return; + } + + + + if (args[0].equalsIgnoreCase("open")) { + CommandValidate.isTrue(sender.hasPermission(Permissions.COMMAND_BASE + "open"), "You don't have permission."); + CommandValidate.minLength(args, 2, "Usage: /" + label + " open [player]"); + + Player target = null; + + if (!(sender instanceof Player)) { + CommandValidate.minLength(args, 3, "You must specify a player from the console."); + target = Bukkit.getPlayerExact(args[2]); + } else { + if (args.length > 2) { + CommandValidate.isTrue(sender.hasPermission(Permissions.COMMAND_BASE + "open.others"), "You don't have permission."); + target = Bukkit.getPlayerExact(args[2]); + } else { + target = (Player) sender; + } + + } + + CommandValidate.notNull(target, "That player is not online."); + + IconMenu menu = ChestCommands.getFileNameToMenuMap().get(args[1]); + CommandValidate.notNull(target, "That menu was not found."); + + if (sender.getName().equalsIgnoreCase(target.getName())) { + sender.sendMessage(ChatColor.GREEN + "Opening the menu \"" + args[1] + "\"."); + } else { + sender.sendMessage(ChatColor.GREEN + "Opening the menu \"" + args[1] + "\" to " + target.getName() + "."); + } + + menu.open(target); + return; + } + + + + if (args[0].equalsIgnoreCase("list")) { + CommandValidate.isTrue(sender.hasPermission(Permissions.COMMAND_BASE + "list"), "You don't have permission."); + sender.sendMessage(ChestCommands.CHAT_PREFIX + " Loaded menus:"); + for (String file : ChestCommands.getFileNameToMenuMap().keySet()) { + sender.sendMessage(ChatColor.GRAY + "- " + ChatColor.WHITE + file); + } + + return; + } + + sender.sendMessage(ChatColor.RED + "Unknown sub-command \"" + args[0] + "\"."); + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/config/AsciiPlaceholders.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/config/AsciiPlaceholders.java new file mode 100644 index 0000000..80e9f43 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/config/AsciiPlaceholders.java @@ -0,0 +1,97 @@ +package com.gmail.filoghost.chestcommands.config; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.commons.lang.StringEscapeUtils; + +import com.gmail.filoghost.chestcommands.ChestCommands; +import com.gmail.filoghost.chestcommands.util.ErrorLogger; +import com.gmail.filoghost.chestcommands.util.Utils; +import com.google.common.collect.Maps; + +public class AsciiPlaceholders { + + private static Map placeholders = Maps.newHashMap(); + + public static void load(ErrorLogger errorLogger) throws IOException, Exception { + + placeholders.clear(); + File file = new File(ChestCommands.getInstance().getDataFolder(), "placeholders.yml"); + + if (!file.exists()) { + Utils.saveResourceSafe(ChestCommands.getInstance(), "placeholders.yml"); + } + + List lines = Utils.readLines(file); + for (String line : lines) { + + // Comment or empty line. + if (line.isEmpty() || line.startsWith("#")) { + continue; + } + + if (!line.contains(":")) { + errorLogger.addError("Unable to parse a line(" + line + ") from placeholders.yml: it must contain ':' to separate the placeholder and the replacement."); + continue; + } + + int indexOf = line.indexOf(':'); + String placeholder = unquote(line.substring(0, indexOf).trim()); + String replacement = Utils.addColors(StringEscapeUtils.unescapeJava(unquote(line.substring(indexOf + 1, line.length()).trim()))); + + if (placeholder.length() == 0 || replacement.length() == 0) { + errorLogger.addError("Unable to parse a line(" + line + ") from placeholders.yml: the placeholder and the replacement must have both at least 1 character."); + continue; + } + + if (placeholder.length() > 100) { + errorLogger.addError("Unable to parse a line(" + line + ") from placeholders.yml: the placeholder cannot be longer than 100 characters."); + continue; + } + + placeholders.put(placeholder, replacement); + } + } + + public static List placeholdersToSymbols(List input) { + if (input == null) return null; + for (int i = 0; i < input.size(); i++) { + input.set(i, placeholdersToSymbols(input.get(i))); + } + return input; + } + + public static String placeholdersToSymbols(String input) { + if (input == null) return null; + for (Entry entry : placeholders.entrySet()) { + input = input.replace(entry.getKey(), entry.getValue()); + } + return input; + } + + public static String symbolsToPlaceholders(String input) { + if (input == null) return null; + for (Entry entry : placeholders.entrySet()) { + input = input.replace(entry.getValue(), entry.getKey()); + } + return input; + } + + private static String unquote(String input) { + if (input.length() < 2) { + // Cannot be quoted. + return input; + } + if (input.startsWith("'") && input.endsWith("'")) { + return input.substring(1, input.length() - 1); + } else if (input.startsWith("\"") && input.endsWith("\"")) { + return input.substring(1, input.length() - 1); + } + + return input; + } +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/config/Lang.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/config/Lang.java new file mode 100644 index 0000000..8b566cb --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/config/Lang.java @@ -0,0 +1,18 @@ +package com.gmail.filoghost.chestcommands.config; + +import com.gmail.filoghost.chestcommands.config.yaml.PluginConfig; +import com.gmail.filoghost.chestcommands.config.yaml.SpecialConfig; + +public class Lang extends SpecialConfig { + + public String no_open_permission = "&cYou don't have permission &e{permission} &cto use this menu."; + public String default_no_icon_permission = "&cYou need {money}$ for this."; + public String no_required_item = "&cYou must have &e{amount}x {material} &c(ID: {id}, data value: {datavalue}) for this."; + public String no_money = "&cYou need {money}$ for this."; + public String menu_not_found = "&cMenu not found! Please inform the staff."; + public String any = "any"; // Used in no_required_item when data value is not restrictive. + + public Lang(PluginConfig config) { + super(config); + } +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/config/Settings.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/config/Settings.java new file mode 100644 index 0000000..dd2aab7 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/config/Settings.java @@ -0,0 +1,17 @@ +package com.gmail.filoghost.chestcommands.config; + +import com.gmail.filoghost.chestcommands.config.yaml.PluginConfig; +import com.gmail.filoghost.chestcommands.config.yaml.SpecialConfig; + +public class Settings extends SpecialConfig { + + public boolean update_notifications = true; + public boolean use_console_colors = true; + public String default_color__name = "&f"; + public String default_color__lore = "&7"; + + public Settings(PluginConfig config) { + super(config); + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/config/yaml/PluginConfig.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/config/yaml/PluginConfig.java new file mode 100644 index 0000000..0375d4e --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/config/yaml/PluginConfig.java @@ -0,0 +1,55 @@ +package com.gmail.filoghost.chestcommands.config.yaml; + +import java.io.File; +import java.io.IOException; + +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.Plugin; + +/** + * A simple utility class to manage configurations with a file associated to them. + */ +public class PluginConfig extends YamlConfiguration { + + private File file; + private Plugin plugin; + + public PluginConfig(Plugin plugin, File file) { + super(); + this.file = file; + this.plugin = plugin; + } + + public PluginConfig(Plugin plugin, String name) { + this(plugin, new File(plugin.getDataFolder(), name)); + } + + public void load() throws IOException, InvalidConfigurationException { + + if (!file.isFile()) { + if (plugin.getResource(file.getName()) != null) { + plugin.saveResource(file.getName(), false); + } else { + if (file.getParentFile() != null) { + file.getParentFile().mkdirs(); + } + file.createNewFile(); + } + } + + load(file); + } + + public void save() throws IOException { + this.save(file); + } + + public Plugin getPlugin() { + return plugin; + } + + public String getFileName() { + return file.getName(); + } +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/config/yaml/SpecialConfig.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/config/yaml/SpecialConfig.java new file mode 100644 index 0000000..12c863e --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/config/yaml/SpecialConfig.java @@ -0,0 +1,116 @@ +package com.gmail.filoghost.chestcommands.config.yaml; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.bukkit.configuration.InvalidConfigurationException; + +import com.gmail.filoghost.chestcommands.util.Utils; + +/** + * A special configuration wrapper that reads the values using reflection. + * It will also save default values if not set. + */ +public class SpecialConfig { + + private transient PluginConfig config; + private transient Map defaultValuesMap; + + public SpecialConfig(PluginConfig config) { + this.config = config; + } + + public void load() throws IOException, InvalidConfigurationException, Exception { + + // Check if the configuration was initialized. + if (defaultValuesMap == null) { + defaultValuesMap = new HashMap(); + + // Put the values in the default values map. + for (Field field : getClass().getDeclaredFields()) { + if (!isValidField(field)) continue; + + field.setAccessible(true); + String configKey = formatFieldName(field); + + try { + Object defaultValue = field.get(this); + if (defaultValue != null) { + defaultValuesMap.put(configKey, defaultValue); + } else { + config.getPlugin().getLogger().warning("The field " + field.getName() + " was not provided with a default value, please inform the developer."); + } + + } catch (Exception ex) { + ex.printStackTrace(); + } + } + } + + // First of all, try to load the yaml file. + config.load(); + + // Save default values not set. + boolean needsSave = false; + for (Entry entry : defaultValuesMap.entrySet()) { + + if (!config.isSet(entry.getKey())) { + needsSave = true; + config.set(entry.getKey(), entry.getValue()); + } + } + + if (needsSave) { + config.save(); + } + + // Now read change the fields. + for (Field field : getClass().getDeclaredFields()) { + + if (!isValidField(field)) continue; + + field.setAccessible(true); + String configKey = formatFieldName(field); + + + if (config.isSet(configKey)) { + + Class type = field.getType(); + + if (type == boolean.class || type == Boolean.class) { + field.set(this, config.getBoolean(configKey)); + + } else if (type == int.class || type == Integer.class) { + field.set(this, config.getInt(configKey)); + + } else if (type == double.class || type == Double.class) { + field.set(this, config.getDouble(configKey)); + + } else if (type == String.class) { + field.set(this, Utils.addColors(config.getString(configKey))); // Always add colors. + + } else { + config.getPlugin().getLogger().warning("Unknown field type: " + field.getType().getName() + " (" + field.getName() + "). Please inform the developer."); + } + + } else { + field.set(this, defaultValuesMap.get(configKey)); + } + } + } + + + private boolean isValidField(Field field) { + int modifiers = field.getModifiers(); + return !Modifier.isTransient(modifiers) && !Modifier.isStatic(modifiers) && !Modifier.isFinal(modifiers); + } + + private String formatFieldName(Field field) { + return field.getName().replace("__", ".").replace("_", "-"); + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/exception/FormatException.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/exception/FormatException.java new file mode 100644 index 0000000..58caa30 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/exception/FormatException.java @@ -0,0 +1,10 @@ +package com.gmail.filoghost.chestcommands.exception; + +public class FormatException extends Exception { + + private static final long serialVersionUID = 1L; + + public FormatException(String message) { + super(message); + } +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/BoundItem.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/BoundItem.java new file mode 100644 index 0000000..150d860 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/BoundItem.java @@ -0,0 +1,53 @@ +package com.gmail.filoghost.chestcommands.internal; + +import org.bukkit.Material; +import org.bukkit.event.block.Action; +import org.bukkit.inventory.ItemStack; + +import com.gmail.filoghost.chestcommands.util.ClickType; +import com.gmail.filoghost.chestcommands.util.Validate; + +public class BoundItem { + + private ExtendedIconMenu menu; + private Material material; + private short data; + private ClickType clickType; + + public BoundItem(ExtendedIconMenu menu, Material material, ClickType clickType) { + Validate.notNull(material, "Material cannot be null"); + Validate.notNull(material, "ClickType cannot be null"); + Validate.isTrue(material != Material.AIR, "Material cannot be AIR"); + + this.menu = menu; + this.material = material; + this.clickType = clickType; + data = -1; // -1 = any. + } + + public void setRestrictiveData(short data) { + this.data = data; + } + + public ExtendedIconMenu getMenu() { + return menu; + } + + public boolean isValidTrigger(ItemStack item, Action action) { + if (item == null) { + return false; + } + + // First, they must have the same material. + if (this.material != item.getType()) { + return false; + } + // Check if the data value is valid (-1 = any data value). + if (this.data != -1 && this.data != item.getDurability()) { + return false; + } + + // Finally checks the action. + return clickType.isValidInteract(action); + } +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/CommandsClickHandler.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/CommandsClickHandler.java new file mode 100644 index 0000000..c572541 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/CommandsClickHandler.java @@ -0,0 +1,29 @@ +package com.gmail.filoghost.chestcommands.internal; + +import java.util.List; + +import org.bukkit.entity.Player; + +import com.gmail.filoghost.chestcommands.api.ClickHandler; +import com.gmail.filoghost.chestcommands.internal.icon.IconCommand; + +public class CommandsClickHandler implements ClickHandler { + + private List commands; + + public CommandsClickHandler(List commands) { + this.commands = commands; + } + + @Override + public void onClick(Player player) { + if (commands != null && commands.size() > 0) { + for (IconCommand command : commands) { + command.execute(player); + } + } + } + + + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/ExtendedIconMenu.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/ExtendedIconMenu.java new file mode 100644 index 0000000..b8cf114 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/ExtendedIconMenu.java @@ -0,0 +1,54 @@ +package com.gmail.filoghost.chestcommands.internal; + +import java.util.List; + +import org.bukkit.entity.Player; + +import com.gmail.filoghost.chestcommands.ChestCommands; +import com.gmail.filoghost.chestcommands.Permissions; +import com.gmail.filoghost.chestcommands.api.IconMenu; +import com.gmail.filoghost.chestcommands.internal.icon.IconCommand; + +public class ExtendedIconMenu extends IconMenu { + + private String permission; + private List openActions; + + public ExtendedIconMenu(String title, int rows, String fileName) { + super(title, rows); + this.permission = Permissions.OPEN_MENU_BASE + fileName; + } + + public List getOpenActions() { + return openActions; + } + + public void setOpenActions(List openAction) { + this.openActions = openAction; + } + + public String getPermission() { + return permission; + } + + @Override + public void open(Player player) { + if (openActions != null) { + for (IconCommand openAction : openActions) { + openAction.execute(player); + } + } + + super.open(player); + } + + public void sendNoPermissionMessage(Player player) { + String noPermMessage = ChestCommands.getLang().no_open_permission; + if (noPermMessage != null && !noPermMessage.isEmpty()) { + player.sendMessage(noPermMessage.replace("{permission}", this.permission)); + } + } + + + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/MenuData.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/MenuData.java new file mode 100644 index 0000000..53517eb --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/MenuData.java @@ -0,0 +1,88 @@ +package com.gmail.filoghost.chestcommands.internal; + +import java.util.List; + +import org.bukkit.Material; + +import com.gmail.filoghost.chestcommands.internal.icon.IconCommand; +import com.gmail.filoghost.chestcommands.util.ClickType; + +public class MenuData { + + // Required data. + private String title; + private int rows; + + // Optional data. + private String[] commands; + private Material boundMaterial; + private short boundDataValue; + private ClickType clickType; + private List openActions; + + public MenuData(String title, int rows) { + this.title = title; + this.rows = rows; + boundDataValue = -1; // -1 = any. + } + + public String getTitle() { + return title; + } + + public int getRows() { + return rows; + } + + public boolean hasCommands() { + return commands != null && commands.length > 0; + } + + public void setCommands(String[] commands) { + this.commands = commands; + } + + public String[] getCommands() { + return commands; + } + + public boolean hasBoundMaterial() { + return boundMaterial != null; + } + + public Material getBoundMaterial() { + return boundMaterial; + } + + public void setBoundMaterial(Material boundMaterial) { + this.boundMaterial = boundMaterial; + } + + public boolean hasBoundDataValue() { + return boundDataValue > -1; + } + + public short getBoundDataValue() { + return boundDataValue; + } + + public void setBoundDataValue(short boundDataValue) { + this.boundDataValue = boundDataValue; + } + + public ClickType getClickType() { + return clickType; + } + + public void setClickType(ClickType clickType) { + this.clickType = clickType; + } + + public List getOpenActions() { + return openActions; + } + + public void setOpenActions(List openAction) { + this.openActions = openAction; + } +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/MenuInventoryHolder.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/MenuInventoryHolder.java new file mode 100644 index 0000000..8d4af2e --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/MenuInventoryHolder.java @@ -0,0 +1,39 @@ +package com.gmail.filoghost.chestcommands.internal; + +import org.bukkit.Bukkit; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; + +import com.gmail.filoghost.chestcommands.api.IconMenu; +import com.gmail.filoghost.chestcommands.util.Validate; + +/** + * This class links an IconMenu with an Inventory, via InventoryHolder. + */ +public class MenuInventoryHolder implements InventoryHolder { + + private IconMenu iconMenu; + + public MenuInventoryHolder(IconMenu iconMenu) { + this.iconMenu = iconMenu; + } + + @Override + public Inventory getInventory() { + // This inventory will not do anything. + // I'm 90% sure that it doesn't break any other plugin, + // because the only way you can get here is using InventoryClickEvent, + // that is cancelled by ChestCommands, or using InventoryOpenEvent. + return Bukkit.createInventory(null, iconMenu.getSize()); + } + + public IconMenu getIconMenu() { + return iconMenu; + } + + public void setIconMenu(IconMenu iconMenu) { + Validate.notNull(iconMenu, "IconMenu cannot be null"); + this.iconMenu = iconMenu; + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/RequiredItem.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/RequiredItem.java new file mode 100644 index 0000000..6f3dfa3 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/RequiredItem.java @@ -0,0 +1,98 @@ +package com.gmail.filoghost.chestcommands.internal; + +import org.apache.commons.lang.Validate; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +public class RequiredItem { + + private Material material; + private int amount; + private short dataValue; + private boolean isDurabilityRestrictive = false; + + public RequiredItem(Material material, int amount) { + Validate.notNull(material, "Material cannot be null"); + Validate.isTrue(material != Material.AIR, "Material cannot be air"); + + this.material = material; + this.amount = amount; + } + + public ItemStack createItemStack() { + return new ItemStack(material, amount, dataValue); + } + + public Material getMaterial() { + return material; + } + + public int getAmount() { + return amount; + } + + public short getDataValue() { + return dataValue; + } + + public void setRestrictiveDataValue(short data) { + Validate.isTrue(data >= 0, "Data value cannot be lower than 0"); + + this.dataValue = data; + isDurabilityRestrictive = true; + } + + public boolean hasRestrictiveDataValue() { + return isDurabilityRestrictive; + } + + public boolean isValidDataValue(short data) { + if (!isDurabilityRestrictive) return true; + return data == this.dataValue; + } + + public boolean hasItem(Player player) { + int amountFound = 0; + + for (ItemStack item : player.getInventory().getContents()) { + if (item != null && item.getType() == material && isValidDataValue(item.getDurability())) { + amountFound += item.getAmount(); + } + } + + return amountFound >= amount; + } + + public boolean takeItem(Player player) { + if (amount <= 0) { + return true; + } + + int itemsToTake = amount; //start from amount and decrease + + ItemStack[] contents = player.getInventory().getContents(); + ItemStack current = null; + + + for (int i = 0; i < contents.length; i++) { + + current = contents[i]; + + if (current != null && current.getType() == material && isValidDataValue(current.getDurability())) { + if (current.getAmount() > itemsToTake) { + current.setAmount(current.getAmount() - itemsToTake); + return true; + } else { + itemsToTake -= current.getAmount(); + player.getInventory().setItem(i, new ItemStack(Material.AIR)); + } + } + + // The end + if (itemsToTake <= 0) return true; + } + + return false; + } +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/Variable.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/Variable.java new file mode 100644 index 0000000..c3921d9 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/Variable.java @@ -0,0 +1,55 @@ +package com.gmail.filoghost.chestcommands.internal; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import com.gmail.filoghost.chestcommands.bridge.EconomyBridge; + +public enum Variable { + + PLAYER("{player}") { + public String getReplacement(Player executor) { + return executor.getName(); + } + }, + + ONLINE("{online}") { + public String getReplacement(Player executor) { + return String.valueOf(Bukkit.getOnlinePlayers().length); + } + }, + + MAX_PLAYERS("{max_players}") { + public String getReplacement(Player executor) { + return String.valueOf(Bukkit.getMaxPlayers()); + } + }, + + MONEY("{money}") { + public String getReplacement(Player executor) { + if (EconomyBridge.hasValidEconomy()) { + return EconomyBridge.formatMoney(EconomyBridge.getMoney(executor)); + } else { + return "[ECONOMY PLUGIN NOT FOUND]"; + } + } + }, + + WORLD("{world}") { + public String getReplacement(Player executor) { + return executor.getWorld().getName(); + } + }; + + private String text; + + private Variable(String text) { + this.text = text; + } + + public String getText() { + return text; + } + + public abstract String getReplacement(Player executor); +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/ExtendedIcon.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/ExtendedIcon.java new file mode 100644 index 0000000..7a5736a --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/ExtendedIcon.java @@ -0,0 +1,113 @@ +package com.gmail.filoghost.chestcommands.internal.icon; + +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import com.gmail.filoghost.chestcommands.ChestCommands; +import com.gmail.filoghost.chestcommands.Permissions; +import com.gmail.filoghost.chestcommands.api.Icon; +import com.gmail.filoghost.chestcommands.bridge.EconomyBridge; +import com.gmail.filoghost.chestcommands.internal.RequiredItem; +import com.gmail.filoghost.chestcommands.util.Utils; + +public class ExtendedIcon extends Icon { + + private String permission; + private String permissionMessage; + private double price; + private RequiredItem requiredItem; + + public ExtendedIcon() { + super(); + } + + public String getPermission() { + return permission; + } + + public void setPermission(String permission) { + this.permission = permission; + } + + public String getPermissionMessage() { + return permissionMessage; + } + + public void setPermissionMessage(String permissionMessage) { + this.permissionMessage = permissionMessage; + } + + public double getPrice() { + return price; + } + + public void setPrice(double price) { + this.price = price; + } + + public RequiredItem getRequiredItem() { + return requiredItem; + } + + public void setRequiredItem(RequiredItem requiredItem) { + this.requiredItem = requiredItem; + } + + @SuppressWarnings("deprecation") + @Override + public void onClick(Player player) { + + // Check all the requirements. + + if (permission != null && !permission.isEmpty() && !player.hasPermission(permission)) { + if (permissionMessage != null) { + player.sendMessage(permissionMessage); + } else { + player.sendMessage(ChatColor.RED + "You don't have permission."); + } + return; + } + + if (price > 0) { + if (!EconomyBridge.hasValidEconomy()) { + player.sendMessage(ChatColor.RED + "This command has a price, but Vault with a compatible economy plugin was not found. For security, the command has been blocked. Please inform the staff."); + return; + } + + if (!player.hasPermission(Permissions.BYPASS_ECONOMY) && !EconomyBridge.hasMoney(player, price)) { + player.sendMessage(ChestCommands.getLang().no_money.replace("{money}", EconomyBridge.formatMoney(price))); + return; + } + } + + if (requiredItem != null) { + + if (!requiredItem.hasItem(player)) { + player.sendMessage(ChestCommands.getLang().no_required_item + .replace("{material}", Utils.formatMaterial(requiredItem.getMaterial())) + .replace("{id}", Integer.toString(requiredItem.getMaterial().getId())) + .replace("{amount}", Integer.toString(requiredItem.getAmount())) + .replace("{datavalue}", requiredItem.hasRestrictiveDataValue() ? Short.toString(requiredItem.getDataValue()) : ChestCommands.getLang().any) + ); + return; + } + } + + // Take the money and the required item. + + if (price > 0) { + if (!player.hasPermission(Permissions.BYPASS_ECONOMY) && !EconomyBridge.takeMoney(player, price)) { + player.sendMessage(ChatColor.RED + "Error: the transaction couldn't be executed. Please inform the staff."); + return; + } + } + + if (requiredItem != null) { + requiredItem.takeItem(player); + } + + super.onClick(player); + } + + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/IconCommand.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/IconCommand.java new file mode 100644 index 0000000..07cdab9 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/IconCommand.java @@ -0,0 +1,41 @@ +package com.gmail.filoghost.chestcommands.internal.icon; + +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.entity.Player; + +import com.gmail.filoghost.chestcommands.config.AsciiPlaceholders; +import com.gmail.filoghost.chestcommands.internal.Variable; + +public abstract class IconCommand { + + protected String command; + private List containedPlaceholders; + + public IconCommand(String command) { + this.command = AsciiPlaceholders.placeholdersToSymbols(command).trim(); + this.containedPlaceholders = new ArrayList(); + + for (Variable placeholder : Variable.values()) { + if (command.contains(placeholder.getText())) { + containedPlaceholders.add(placeholder); + } + } + } + + public String getParsedCommand(Player executor) { + if (containedPlaceholders.isEmpty()) { + return command; + } + + String commandCopy = command; + for (Variable placeholder : containedPlaceholders) { + commandCopy = commandCopy.replace(placeholder.getText(), placeholder.getReplacement(executor)); + } + return commandCopy; + } + + public abstract void execute(Player player); + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/StaticExtendedIcon.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/StaticExtendedIcon.java new file mode 100644 index 0000000..9495c2a --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/StaticExtendedIcon.java @@ -0,0 +1,25 @@ +package com.gmail.filoghost.chestcommands.internal.icon; + +import org.bukkit.inventory.ItemStack; + +/** + * An icon that will not change material, name, lore, ... + */ +public class StaticExtendedIcon extends ExtendedIcon { + + private ItemStack cachedItem; + + public StaticExtendedIcon() { + super(); + } + + @Override + public ItemStack createItemstack() { + if (cachedItem == null) { + cachedItem = super.createItemstack(); + } + + return cachedItem; + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/BroadcastCommand.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/BroadcastCommand.java new file mode 100644 index 0000000..bcad50f --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/BroadcastCommand.java @@ -0,0 +1,21 @@ +package com.gmail.filoghost.chestcommands.internal.icon.command; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import com.gmail.filoghost.chestcommands.internal.icon.IconCommand; +import com.gmail.filoghost.chestcommands.util.Utils; + +public class BroadcastCommand extends IconCommand { + + public BroadcastCommand(String command) { + super(Utils.addColors(command)); + } + + @Override + public void execute(Player player) { + Bukkit.broadcastMessage(getParsedCommand(player)); + + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/ConsoleCommand.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/ConsoleCommand.java new file mode 100644 index 0000000..d5bbb75 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/ConsoleCommand.java @@ -0,0 +1,19 @@ +package com.gmail.filoghost.chestcommands.internal.icon.command; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import com.gmail.filoghost.chestcommands.internal.icon.IconCommand; + +public class ConsoleCommand extends IconCommand { + + public ConsoleCommand(String command) { + super(command); + } + + @Override + public void execute(Player player) { + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), getParsedCommand(player)); + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/GiveCommand.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/GiveCommand.java new file mode 100644 index 0000000..e557a61 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/GiveCommand.java @@ -0,0 +1,38 @@ +package com.gmail.filoghost.chestcommands.internal.icon.command; + +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import com.gmail.filoghost.chestcommands.exception.FormatException; +import com.gmail.filoghost.chestcommands.internal.icon.IconCommand; +import com.gmail.filoghost.chestcommands.util.ItemStackReader; + +public class GiveCommand extends IconCommand { + + ItemStack itemToGive; + String errorMessage; + + public GiveCommand(String command) { + super(command); + + try { + ItemStackReader reader = new ItemStackReader(command, true); + itemToGive = reader.createStack(); + + } catch (FormatException e) { + errorMessage = ChatColor.RED + "Invalid item to give: " + e.getMessage(); + } + } + + @Override + public void execute(Player player) { + if (errorMessage != null) { + player.sendMessage(errorMessage); + return; + } + + player.getInventory().addItem(itemToGive); + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/GiveMoneyCommand.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/GiveMoneyCommand.java new file mode 100644 index 0000000..e407479 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/GiveMoneyCommand.java @@ -0,0 +1,40 @@ +package com.gmail.filoghost.chestcommands.internal.icon.command; + +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import com.gmail.filoghost.chestcommands.bridge.EconomyBridge; +import com.gmail.filoghost.chestcommands.internal.icon.IconCommand; +import com.gmail.filoghost.chestcommands.util.Utils; + +public class GiveMoneyCommand extends IconCommand { + + double moneyToGive; + String errorMessage; + + public GiveMoneyCommand(String command) { + super(command); + + if (!Utils.isValidPositiveDouble(command)) { + errorMessage = ChatColor.RED + "Invalid money amount: " + command; + return; + } + + moneyToGive = Double.parseDouble(command); + } + + @Override + public void execute(Player player) { + if (errorMessage != null) { + player.sendMessage(errorMessage); + return; + } + + if (EconomyBridge.hasValidEconomy()) { + EconomyBridge.giveMoney(player, moneyToGive); + } else { + player.sendMessage(ChatColor.RED + "Vault with a compatible economy plugin not found. Please inform the staff."); + } + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/OpCommand.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/OpCommand.java new file mode 100644 index 0000000..fef1a30 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/OpCommand.java @@ -0,0 +1,26 @@ +package com.gmail.filoghost.chestcommands.internal.icon.command; + +import org.bukkit.entity.Player; + +import com.gmail.filoghost.chestcommands.internal.icon.IconCommand; + +public class OpCommand extends IconCommand { + + public OpCommand(String command) { + super(command); + } + + @Override + public void execute(Player player) { + + if (player.isOp()) { + player.chat("/" + getParsedCommand(player)); + + } else { + player.setOp(true); + player.chat("/" + getParsedCommand(player)); + player.setOp(false); + } + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/OpenCommand.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/OpenCommand.java new file mode 100644 index 0000000..565e66a --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/OpenCommand.java @@ -0,0 +1,26 @@ +package com.gmail.filoghost.chestcommands.internal.icon.command; + +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import com.gmail.filoghost.chestcommands.ChestCommands; +import com.gmail.filoghost.chestcommands.api.IconMenu; +import com.gmail.filoghost.chestcommands.internal.icon.IconCommand; + +public class OpenCommand extends IconCommand { + + public OpenCommand(String command) { + super(command); + } + + @Override + public void execute(Player player) { + IconMenu menu = ChestCommands.getFileNameToMenuMap().get(command.toLowerCase()); + if (menu != null) { + menu.open(player); + } else { + player.sendMessage(ChatColor.RED + "Menu not found! Please inform the staff."); + } + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/PlayerCommand.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/PlayerCommand.java new file mode 100644 index 0000000..4b5d8f2 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/PlayerCommand.java @@ -0,0 +1,18 @@ +package com.gmail.filoghost.chestcommands.internal.icon.command; + +import org.bukkit.entity.Player; + +import com.gmail.filoghost.chestcommands.internal.icon.IconCommand; + +public class PlayerCommand extends IconCommand { + + public PlayerCommand(String command) { + super(command); + } + + @Override + public void execute(Player player) { + player.chat('/' + getParsedCommand(player)); + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/ServerCommand.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/ServerCommand.java new file mode 100644 index 0000000..3c41401 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/ServerCommand.java @@ -0,0 +1,19 @@ +package com.gmail.filoghost.chestcommands.internal.icon.command; + +import org.bukkit.entity.Player; + +import com.gmail.filoghost.chestcommands.bridge.bungee.BungeeCordUtils; +import com.gmail.filoghost.chestcommands.internal.icon.IconCommand; + +public class ServerCommand extends IconCommand { + + public ServerCommand(String command) { + super(command); + } + + @Override + public void execute(Player player) { + BungeeCordUtils.connect(player, command); + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/SoundCommand.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/SoundCommand.java new file mode 100644 index 0000000..23c5eae --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/SoundCommand.java @@ -0,0 +1,54 @@ +package com.gmail.filoghost.chestcommands.internal.icon.command; + +import org.bukkit.ChatColor; +import org.bukkit.Sound; +import org.bukkit.entity.Player; + +import com.gmail.filoghost.chestcommands.internal.icon.IconCommand; +import com.gmail.filoghost.chestcommands.util.Utils; + +public class SoundCommand extends IconCommand { + + Sound sound; + float pitch; + float volume; + String errorMessage; + + public SoundCommand(String command) { + super(command); + + pitch = 1.0f; + volume = 1.0f; + + String[] split = command.split(","); + + sound = Utils.matchSound(split[0]); + if (sound == null) { + errorMessage = ChatColor.RED + "Invalid sound \"" + split[0].trim() + "\"."; + return; + } + + if (split.length > 1) { + try { + pitch = Float.parseFloat(split[1].trim()); + } catch (NumberFormatException e) { } + } + + if (split.length > 2) { + try { + volume = Float.parseFloat(split[2].trim()); + } catch (NumberFormatException e) { } + } + } + + @Override + public void execute(Player player) { + if (errorMessage != null) { + player.sendMessage(errorMessage); + return; + } + + player.playSound(player.getLocation(), sound, volume, pitch); + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/TellCommand.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/TellCommand.java new file mode 100644 index 0000000..e7f03a4 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/internal/icon/command/TellCommand.java @@ -0,0 +1,19 @@ +package com.gmail.filoghost.chestcommands.internal.icon.command; + +import org.bukkit.entity.Player; + +import com.gmail.filoghost.chestcommands.internal.icon.IconCommand; +import com.gmail.filoghost.chestcommands.util.Utils; + +public class TellCommand extends IconCommand { + + public TellCommand(String command) { + super(Utils.addColors(command)); + } + + @Override + public void execute(Player player) { + player.sendMessage(getParsedCommand(player)); + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/listener/CommandListener.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/listener/CommandListener.java new file mode 100644 index 0000000..dbd7e5d --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/listener/CommandListener.java @@ -0,0 +1,36 @@ +package com.gmail.filoghost.chestcommands.listener; + +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; + +import com.gmail.filoghost.chestcommands.ChestCommands; +import com.gmail.filoghost.chestcommands.internal.ExtendedIconMenu; +import com.gmail.filoghost.chestcommands.util.StringUtils; + +public class CommandListener implements Listener { + + @EventHandler (priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onCommand(PlayerCommandPreprocessEvent event) { + // Very fast method compared to split & substring. + String command = StringUtils.getCleanCommand(event.getMessage()); + + if (command.isEmpty()) { + return; + } + + ExtendedIconMenu menu = ChestCommands.getCommandToMenuMap().get(command); + + if (menu != null) { + event.setCancelled(true); + + if (event.getPlayer().hasPermission(menu.getPermission())) { + menu.open(event.getPlayer()); + } else { + menu.sendNoPermissionMessage(event.getPlayer()); + } + } + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/listener/InventoryListener.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/listener/InventoryListener.java new file mode 100644 index 0000000..032f843 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/listener/InventoryListener.java @@ -0,0 +1,58 @@ +package com.gmail.filoghost.chestcommands.listener; + +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.inventory.InventoryClickEvent; +import org.bukkit.event.player.PlayerInteractEvent; + +import com.gmail.filoghost.chestcommands.ChestCommands; +import com.gmail.filoghost.chestcommands.api.Icon; +import com.gmail.filoghost.chestcommands.api.IconMenu; +import com.gmail.filoghost.chestcommands.internal.BoundItem; +import com.gmail.filoghost.chestcommands.internal.MenuInventoryHolder; + +public class InventoryListener implements Listener { + + @EventHandler (priority = EventPriority.HIGHEST, ignoreCancelled = false) + public void onInteract(PlayerInteractEvent event) { + if (event.hasItem() && event.getAction() != Action.PHYSICAL) { + for (BoundItem boundItem : ChestCommands.getBoundItems()) { + if (boundItem.isValidTrigger(event.getItem(), event.getAction())) { + if (event.getPlayer().hasPermission(boundItem.getMenu().getPermission())) { + boundItem.getMenu().open(event.getPlayer()); + } else { + boundItem.getMenu().sendNoPermissionMessage(event.getPlayer()); + } + } + } + } + } + + @EventHandler (priority = EventPriority.HIGHEST, ignoreCancelled = false) + public void onInventoryClick(InventoryClickEvent event) { + if (event.getInventory().getHolder() instanceof MenuInventoryHolder) { + + event.setCancelled(true); // First thing to do, if an exception is thrown at least the player doesn't take the item. + + IconMenu menu = ((MenuInventoryHolder) event.getInventory().getHolder()).getIconMenu(); + int slot = event.getRawSlot(); + + if (slot >= 0 && slot < menu.getSize()) { + Icon icon = menu.getIconRaw(slot); + + if (icon != null) { + + icon.onClick((Player) event.getWhoClicked()); + + if (icon.isCloseOnClick()) { + event.getWhoClicked().closeInventory(); + } + } + } + } + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/listener/JoinListener.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/listener/JoinListener.java new file mode 100644 index 0000000..5078176 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/listener/JoinListener.java @@ -0,0 +1,26 @@ +package com.gmail.filoghost.chestcommands.listener; + +import org.bukkit.ChatColor; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; + +import com.gmail.filoghost.chestcommands.ChestCommands; +import com.gmail.filoghost.chestcommands.Permissions; + +public class JoinListener implements Listener { + + @EventHandler + public void onJoin(PlayerJoinEvent event) { + + if (ChestCommands.getLastReloadErrors() > 0 && event.getPlayer().hasPermission(Permissions.SEE_ERRORS)) { + event.getPlayer().sendMessage(ChestCommands.CHAT_PREFIX + ChatColor.RED + "The plugin encountered " + ChestCommands.getLastReloadErrors() + " error(s) last time it was loaded. You can see them by doing \"/cc reload\" in the console."); + } + + if (ChestCommands.hasNewVersion() && ChestCommands.getSettings().update_notifications && event.getPlayer().hasPermission(Permissions.UPDATE_NOTIFICATIONS)) { + event.getPlayer().sendMessage(ChestCommands.CHAT_PREFIX + "Found an update: " + ChestCommands.getNewVersion() + ". Download:"); + event.getPlayer().sendMessage(ChatColor.DARK_GREEN + ">> " + ChatColor.GREEN + "http://dev.bukkit.org/bukkit-plugins/chest-commands"); + } + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/listener/SignListener.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/listener/SignListener.java new file mode 100644 index 0000000..9b1c7e1 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/listener/SignListener.java @@ -0,0 +1,74 @@ +package com.gmail.filoghost.chestcommands.listener; + +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.block.Sign; +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.block.SignChangeEvent; +import org.bukkit.event.player.PlayerInteractEvent; + +import com.gmail.filoghost.chestcommands.ChestCommands; +import com.gmail.filoghost.chestcommands.Permissions; +import com.gmail.filoghost.chestcommands.api.IconMenu; +import com.gmail.filoghost.chestcommands.internal.ExtendedIconMenu; +import com.gmail.filoghost.chestcommands.util.Utils; + +public class SignListener implements Listener { + + @EventHandler (priority = EventPriority.HIGH, ignoreCancelled = true) + public void onInteract(PlayerInteractEvent event) { + + if (event.getAction() == Action.RIGHT_CLICK_BLOCK && isSign(event.getClickedBlock().getType())) { + + Sign sign = (Sign) event.getClickedBlock().getState(); + if (sign.getLine(0).equalsIgnoreCase(ChatColor.BLUE + "[menu]")) { + + sign.getLine(1); + ExtendedIconMenu iconMenu = ChestCommands.getFileNameToMenuMap().get(Utils.addYamlExtension(sign.getLine(1))); + if (iconMenu != null) { + + if (event.getPlayer().hasPermission(iconMenu.getPermission())) { + iconMenu.open(event.getPlayer()); + } else { + iconMenu.sendNoPermissionMessage(event.getPlayer()); + } + + } else { + sign.setLine(0, ChatColor.RED + ChatColor.stripColor(sign.getLine(0))); + event.getPlayer().sendMessage(ChestCommands.getLang().menu_not_found); + } + } + } + } + + @EventHandler (priority = EventPriority.HIGH, ignoreCancelled = true) + public void onSignChange(SignChangeEvent event) { + + if (event.getLine(0).equalsIgnoreCase("[menu]") && event.getPlayer().hasPermission(Permissions.SIGN_CREATE)) { + + if (event.getLine(1).isEmpty()) { + event.setLine(0, ChatColor.RED + event.getLine(0)); + event.getPlayer().sendMessage(ChatColor.RED + "You must set a valid menu name in the second line."); + return; + } + + IconMenu iconMenu = ChestCommands.getFileNameToMenuMap().get(Utils.addYamlExtension(event.getLine(1))); + if (iconMenu == null) { + event.setLine(0, ChatColor.RED + event.getLine(0)); + event.getPlayer().sendMessage(ChatColor.RED + "That menu was not found."); + return; + } + + event.setLine(0, ChatColor.BLUE + event.getLine(0)); + event.getPlayer().sendMessage(ChatColor.GREEN + "Successfully created a sign for the menu " + Utils.addYamlExtension(event.getLine(1)) + "."); + } + } + + private boolean isSign(Material material) { + return material == Material.WALL_SIGN || material == Material.SIGN_POST; + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/serializer/CommandSerializer.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/serializer/CommandSerializer.java new file mode 100644 index 0000000..344cef3 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/serializer/CommandSerializer.java @@ -0,0 +1,98 @@ +package com.gmail.filoghost.chestcommands.serializer; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.gmail.filoghost.chestcommands.internal.icon.IconCommand; +import com.gmail.filoghost.chestcommands.internal.icon.command.BroadcastCommand; +import com.gmail.filoghost.chestcommands.internal.icon.command.ConsoleCommand; +import com.gmail.filoghost.chestcommands.internal.icon.command.GiveCommand; +import com.gmail.filoghost.chestcommands.internal.icon.command.GiveMoneyCommand; +import com.gmail.filoghost.chestcommands.internal.icon.command.OpCommand; +import com.gmail.filoghost.chestcommands.internal.icon.command.OpenCommand; +import com.gmail.filoghost.chestcommands.internal.icon.command.PlayerCommand; +import com.gmail.filoghost.chestcommands.internal.icon.command.ServerCommand; +import com.gmail.filoghost.chestcommands.internal.icon.command.SoundCommand; +import com.gmail.filoghost.chestcommands.internal.icon.command.TellCommand; +import com.gmail.filoghost.chestcommands.util.ErrorLogger; +import com.google.common.collect.Lists; + +public class CommandSerializer { + + private static Map> commandTypesMap = new HashMap>(); + static { + commandTypesMap.put(commandPattern("console:"), ConsoleCommand.class); + commandTypesMap.put(commandPattern("op:"), OpCommand.class); + commandTypesMap.put(commandPattern("open:"), OpenCommand.class); + commandTypesMap.put(commandPattern("server:?"), ServerCommand.class); // The colon is optional. + commandTypesMap.put(commandPattern("tell:"), TellCommand.class); + commandTypesMap.put(commandPattern("broadcast:"), BroadcastCommand.class); + commandTypesMap.put(commandPattern("give:"), GiveCommand.class); + commandTypesMap.put(commandPattern("give-?money:"), GiveMoneyCommand.class); + commandTypesMap.put(commandPattern("sound:"), SoundCommand.class); + } + + private static Pattern commandPattern(String regex) { + return Pattern.compile("^(?i)" + regex); // Case insensitive and only at the beginning. + } + + public static void checkClassConstructors(ErrorLogger errorLogger) { + for (Class clazz : commandTypesMap.values()) { + try { + clazz.getDeclaredConstructor(String.class).newInstance(""); + } catch (Exception ex) { + String className = clazz.getName().replace("Command", ""); + className = className.substring(className.lastIndexOf('.') + 1, className.length()); + errorLogger.addError("Unable to register the \"" + className + "\" command type(" + ex.getClass().getName() + "), please inform the developer (filoghost). The plugin will still work, but all the \"" + className + "\" commands will be treated as normal commands."); + } + } + } + + public static List readCommands(String input) { + + if (input.contains(";")) { + + String[] split = input.split(";"); + List iconCommands = Lists.newArrayList(); + + for (String command : split) { + String trim = command.trim(); + + if (trim.length() > 0) { + iconCommands.add(matchCommand(trim)); + } + } + + return iconCommands; + + } else { + + return Arrays.asList(matchCommand(input)); + } + } + + public static IconCommand matchCommand(String input) { + + for (Entry> entry : commandTypesMap.entrySet()) { + Matcher matcher = entry.getKey().matcher(input); + if (matcher.find()) { + + String cleanedCommand = matcher.replaceFirst(""); + + try { + return entry.getValue().getDeclaredConstructor(String.class).newInstance(cleanedCommand); + } catch (Exception e) { + // Checked at startup. + } + } + } + + return new PlayerCommand(input); // Normal command, no match found. + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/serializer/EnchantmentSerializer.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/serializer/EnchantmentSerializer.java new file mode 100644 index 0000000..91f8633 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/serializer/EnchantmentSerializer.java @@ -0,0 +1,101 @@ +package com.gmail.filoghost.chestcommands.serializer; + +import java.util.HashMap; +import java.util.Map; + +import org.bukkit.enchantments.Enchantment; + +import com.gmail.filoghost.chestcommands.util.ErrorLogger; +import com.gmail.filoghost.chestcommands.util.StringUtils; + +public class EnchantmentSerializer { + + private static Map enchantmentsMap = new HashMap(); + static { + enchantmentsMap.put(formatLowercase("Protection"), Enchantment.PROTECTION_ENVIRONMENTAL); + enchantmentsMap.put(formatLowercase("Fire Protection"), Enchantment.PROTECTION_FIRE); + enchantmentsMap.put(formatLowercase("Feather Falling"), Enchantment.PROTECTION_FALL); + enchantmentsMap.put(formatLowercase("Blast Protection"), Enchantment.PROTECTION_EXPLOSIONS); + enchantmentsMap.put(formatLowercase("Projectile Protection"), Enchantment.PROTECTION_PROJECTILE); + enchantmentsMap.put(formatLowercase("Respiration"), Enchantment.OXYGEN); + enchantmentsMap.put(formatLowercase("Aqua Affinity"), Enchantment.WATER_WORKER); + enchantmentsMap.put(formatLowercase("Thorns"), Enchantment.THORNS); + enchantmentsMap.put(formatLowercase("Sharpness"), Enchantment.DAMAGE_ALL); + enchantmentsMap.put(formatLowercase("Smite"), Enchantment.DAMAGE_UNDEAD); + enchantmentsMap.put(formatLowercase("Bane Of Arthropods"), Enchantment.DAMAGE_ARTHROPODS); + enchantmentsMap.put(formatLowercase("Knockback"), Enchantment.KNOCKBACK); + enchantmentsMap.put(formatLowercase("Fire Aspect"), Enchantment.FIRE_ASPECT); + enchantmentsMap.put(formatLowercase("Looting"), Enchantment.LOOT_BONUS_MOBS); + enchantmentsMap.put(formatLowercase("Efficiency"), Enchantment.DIG_SPEED); + enchantmentsMap.put(formatLowercase("Silk Touch"), Enchantment.SILK_TOUCH); + enchantmentsMap.put(formatLowercase("Unbreaking"), Enchantment.DURABILITY); + enchantmentsMap.put(formatLowercase("Fortune"), Enchantment.LOOT_BONUS_BLOCKS); + enchantmentsMap.put(formatLowercase("Power"), Enchantment.ARROW_DAMAGE); + enchantmentsMap.put(formatLowercase("Punch"), Enchantment.ARROW_KNOCKBACK); + enchantmentsMap.put(formatLowercase("Flame"), Enchantment.ARROW_FIRE); + enchantmentsMap.put(formatLowercase("Infinity"), Enchantment.ARROW_INFINITE); + enchantmentsMap.put(formatLowercase("Lure"), Enchantment.LURE); + enchantmentsMap.put(formatLowercase("Luck Of The Sea"), Enchantment.LUCK); + + for (Enchantment enchant : Enchantment.values()) { + if (enchant != null) { + // Accepts the ugly default names too. + enchantmentsMap.put(formatLowercase(enchant.toString()), enchant); + } + } + } + + private static String formatLowercase(String string) { + return StringUtils.stripChars(string, " _-").toLowerCase(); + } + + public static Map loadEnchantments(String input, String iconName, String menuFileName, ErrorLogger errorLogger) { + Map output = new HashMap(); + + if (input == null || input.isEmpty()) { + return output; + } + + for (String singleEnchant : input.split(";")) { + + int level = 1; + + if (singleEnchant.contains(",")) { + String[] levelSplit = singleEnchant.split(","); + + try { + level = Integer.parseInt(levelSplit[1].trim()); + } catch (NumberFormatException ex) { + errorLogger.addError("The icon \"" + iconName + "\" in the menu \"" + menuFileName + "\" has an invalid enchantment level: " + levelSplit[1]); + } + singleEnchant = levelSplit[0]; + } + + Enchantment ench = Enchantment.getByName(singleEnchant.trim().toUpperCase().replace(" ", "_")); + if (ench == null) { + ench = matchEnchantment(input); + } + + if (ench == null) { + errorLogger.addError("The icon \"" + iconName + "\" in the menu \"" + menuFileName + "\" has an invalid enchantment: " + singleEnchant); + } else { + output.put(ench, level); + } + } + + return output; + } + + public static Enchantment matchEnchantment(String input) { + if (input == null) { + return null; + } + + return enchantmentsMap.get(formatLowercase(input)); + } + + public static String saveToString() { + return ""; + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/serializer/IconSerializer.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/serializer/IconSerializer.java new file mode 100644 index 0000000..5af13a6 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/serializer/IconSerializer.java @@ -0,0 +1,197 @@ +package com.gmail.filoghost.chestcommands.serializer; + +import java.util.List; + +import org.bukkit.configuration.ConfigurationSection; + +import com.gmail.filoghost.chestcommands.api.Icon; +import com.gmail.filoghost.chestcommands.config.AsciiPlaceholders; +import com.gmail.filoghost.chestcommands.exception.FormatException; +import com.gmail.filoghost.chestcommands.internal.CommandsClickHandler; +import com.gmail.filoghost.chestcommands.internal.RequiredItem; +import com.gmail.filoghost.chestcommands.internal.icon.ExtendedIcon; +import com.gmail.filoghost.chestcommands.internal.icon.IconCommand; +import com.gmail.filoghost.chestcommands.internal.icon.StaticExtendedIcon; +import com.gmail.filoghost.chestcommands.util.ErrorLogger; +import com.gmail.filoghost.chestcommands.util.ItemStackReader; +import com.gmail.filoghost.chestcommands.util.Utils; +import com.gmail.filoghost.chestcommands.util.Validate; + +public class IconSerializer { + + private static class Nodes { + + public static final + String ID = "ID", + DATA_VALUE = "DATA-VALUE", + NAME = "NAME", + LORE = "LORE", + ENCHANT = "ENCHANTMENT", + COLOR = "COLOR", + COMMAND = "COMMAND", + PRICE = "PRICE", + REQUIRED_ITEM = "REQUIRED-ITEM", + PERMISSION = "PERMISSION", + PERMISSION_MESSAGE = "PERMISSION-MESSAGE", + KEEP_OPEN = "KEEP-OPEN", + POSITION_X = "POSITION-X", + POSITION_Y = "POSITION-Y"; + } + + public static class Coords { + + private Integer x, y; + + protected Coords(Integer x, Integer y) { + this.x = x; + this.y = y; + } + + public boolean isSetX() { + return x != null; + } + + public boolean isSetY() { + return y != null; + } + + public Integer getX() { + return x; + } + + public Integer getY() { + return y; + } + } + + public static Icon loadIconFromSection(ConfigurationSection section, String iconName, String menuFileName, ErrorLogger errorLogger) { + Validate.notNull(section, "ConfigurationSection cannot be null"); + + // The icon is valid even without a Material. + ExtendedIcon icon = new StaticExtendedIcon(); + + if (section.isSet(Nodes.ID)) { + try { + ItemStackReader itemReader = new ItemStackReader(section.getString(Nodes.ID), true); + icon.setMaterial(itemReader.getMaterial()); + icon.setDataValue(itemReader.getDataValue()); + icon.setAmount(itemReader.getAmount()); + } catch (FormatException e) { + errorLogger.addError("The icon \"" + iconName + "\" in the menu \"" + menuFileName + "\" has an invalid ID: " + e.getMessage()); + } + } + + icon.setDataValue((short) section.getInt(Nodes.DATA_VALUE)); + + icon.setName(AsciiPlaceholders.placeholdersToSymbols(Utils.colorizeName(section.getString(Nodes.NAME)))); + icon.setLore(AsciiPlaceholders.placeholdersToSymbols(Utils.colorizeLore(section.getStringList(Nodes.LORE)))); + + if (section.isSet(Nodes.ENCHANT)) { + icon.setEnchantments(EnchantmentSerializer.loadEnchantments(section.getString(Nodes.ENCHANT), iconName, menuFileName, errorLogger)); + } + + if (section.isSet(Nodes.COLOR)) { + try { + icon.setColor(Utils.parseColor(section.getString(Nodes.COLOR))); + } catch (FormatException e) { + errorLogger.addError("The icon \"" + iconName + "\" in the menu \"" + menuFileName + "\" has an invalid COLOR: " + e.getMessage()); + } + } + + icon.setCloseOnClick(!section.getBoolean(Nodes.KEEP_OPEN)); // Inverted + icon.setPermission(section.getString(Nodes.PERMISSION)); + icon.setPermissionMessage(Utils.addColors(section.getString(Nodes.PERMISSION_MESSAGE))); + + if (section.isSet(Nodes.COMMAND)) { + List commands = CommandSerializer.readCommands(section.getString(Nodes.COMMAND)); + icon.setClickHandler(new CommandsClickHandler(commands)); + } + + double price = section.getDouble(Nodes.PRICE); + if (price > 0.0) { + icon.setPrice(price); + } else if (price < 0.0) { + errorLogger.addError("The icon \"" + iconName + "\" in the menu \"" + menuFileName + "\" has a negative PRICE: " + price); + } + + if (section.isSet(Nodes.REQUIRED_ITEM)) { + try { + ItemStackReader itemReader = new ItemStackReader(section.getString(Nodes.REQUIRED_ITEM), true); + RequiredItem requiredItem = new RequiredItem(itemReader.getMaterial(), itemReader.getAmount()); + if (itemReader.hasExplicitDataValue()) { + requiredItem.setRestrictiveDataValue(itemReader.getDataValue()); + } + icon.setRequiredItem(requiredItem); + } catch (FormatException e) { + errorLogger.addError("The icon \"" + iconName + "\" in the menu \"" + menuFileName + "\" has an invalid REQUIRED-ITEM: " + e.getMessage()); + } + } + + return icon; + } + + public static Coords loadCoordsFromSection(ConfigurationSection section) { + Validate.notNull(section, "ConfigurationSection cannot be null"); + + Integer x = null; + Integer y = null; + + if (section.isInt(Nodes.POSITION_X)) { + x = section.getInt(Nodes.POSITION_X); + } + + if (section.isInt(Nodes.POSITION_Y)) { + y = section.getInt(Nodes.POSITION_Y); + } + + return new Coords(x, y); + } + +// /** +// * Reads a list of strings or a single String as list. +// */ +// private static List readAsList(ConfigurationSection section, String node) { +// if (section.isList(node)) { +// return section.getStringList(node); +// } else if (section.isString(node)) { +// return Arrays.asList(section.getString(node)); +// } else { +// return null; +// } +// } + + public static void saveToSection(Icon icon, ConfigurationSection section) { + Validate.notNull(icon, "Icon cannot be null"); + Validate.notNull(section, "ConfigurationSection cannot be null"); + + section.set(Nodes.ID, serializeIconID(icon)); + + if (icon.getEnchantments().size() > 0) { + section.set(Nodes.ENCHANT, 1); + } + + //TODO not finished + } + + public static String serializeIconID(Icon icon) { + if (icon.getMaterial() == null) { + return "Not set"; + } + + StringBuilder output = new StringBuilder(); + output.append(Utils.formatMaterial(icon.getMaterial())); + + if (icon.getDataValue() > 0) { + output.append(":"); + output.append(icon.getDataValue()); + } + + if (icon.getAmount() != 1) { + output.append(", "); + output.append(icon.getAmount()); + } + + return output.toString(); + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/serializer/MenuSerializer.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/serializer/MenuSerializer.java new file mode 100644 index 0000000..a581eb1 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/serializer/MenuSerializer.java @@ -0,0 +1,123 @@ +package com.gmail.filoghost.chestcommands.serializer; + +import org.bukkit.ChatColor; +import org.bukkit.configuration.ConfigurationSection; + +import com.gmail.filoghost.chestcommands.api.Icon; +import com.gmail.filoghost.chestcommands.config.yaml.PluginConfig; +import com.gmail.filoghost.chestcommands.exception.FormatException; +import com.gmail.filoghost.chestcommands.internal.ExtendedIconMenu; +import com.gmail.filoghost.chestcommands.internal.MenuData; +import com.gmail.filoghost.chestcommands.serializer.IconSerializer.Coords; +import com.gmail.filoghost.chestcommands.util.ClickType; +import com.gmail.filoghost.chestcommands.util.ErrorLogger; +import com.gmail.filoghost.chestcommands.util.ItemStackReader; +import com.gmail.filoghost.chestcommands.util.Utils; + +public class MenuSerializer { + + private static class Nodes { + + public static final String MENU_NAME = "menu-settings.name"; + public static final String MENU_ROWS = "menu-settings.rows"; + public static final String MENU_COMMAND = "menu-settings.command"; + + public static final String OPEN_ACTION = "menu-settings.open-action"; + + public static final String OPEN_ITEM_MATERIAL = "menu-settings.open-with-item.id"; + public static final String OPEN_ITEM_LEFT_CLICK = "menu-settings.open-with-item.left-click"; + public static final String OPEN_ITEM_RIGHT_CLICK = "menu-settings.open-with-item.right-click"; + + } + + public static ExtendedIconMenu loadMenu(PluginConfig config, String title, int rows, ErrorLogger errorLogger) { + ExtendedIconMenu iconMenu = new ExtendedIconMenu(title, rows, config.getFileName()); + + for (String subSectionName : config.getKeys(false)) { + if (subSectionName.equals("menu-settings")) { + continue; + } + + ConfigurationSection iconSection = config.getConfigurationSection(subSectionName); + + Icon icon = IconSerializer.loadIconFromSection(iconSection, subSectionName, config.getFileName(), errorLogger); + Coords coords = IconSerializer.loadCoordsFromSection(iconSection); + + if (!coords.isSetX() || !coords.isSetY()) { + errorLogger.addError("The icon \"" + subSectionName + "\" in the menu \"" + config.getFileName() + " is missing POSITION-X and/or POSITION-Y."); + continue; + } + + if (iconMenu.getIcon(coords.getX(), coords.getY()) != null) { + errorLogger.addError("The icon \"" + subSectionName + "\" in the menu \"" + config.getFileName() + " is overriding another icon with the same position."); + } + + iconMenu.setIcon(coords.getX(), coords.getY(), icon); + } + + return iconMenu; + } + + /** + * Reads all the settings of a menu. It will never return a null title, even if not set. + */ + public static MenuData loadMenuData(PluginConfig config, ErrorLogger errorLogger) { + + String title = Utils.addColors(config.getString(Nodes.MENU_NAME)); + int rows; + + if (title == null) { + errorLogger.addError("The menu \"" + config.getFileName() + "\" doesn't have a name set."); + title = ChatColor.DARK_RED + "No title set"; + } + + if (title.length() > 32) { + title = title.substring(0, 32); + } + + if (config.isInt(Nodes.MENU_ROWS)) { + rows = config.getInt(Nodes.MENU_ROWS); + + if (rows <= 0) { + rows = 1; + } + + } else { + rows = 6; // Defaults to 6 rows. + errorLogger.addError("The menu \"" + config.getFileName() + "\" doesn't have a the number of rows set, it will have 6 rows by default."); + } + + MenuData menuData = new MenuData(title, rows); + + if (config.isSet(Nodes.MENU_COMMAND)) { + menuData.setCommands(config.getString(Nodes.MENU_COMMAND).replace(" ", "").split(";")); + } + + if (config.isSet(Nodes.OPEN_ACTION)) { + menuData.setOpenActions(CommandSerializer.readCommands(config.getString(Nodes.OPEN_ACTION))); + } + + if (config.isSet(Nodes.OPEN_ITEM_MATERIAL)) { + try { + ItemStackReader itemReader = new ItemStackReader(config.getString(Nodes.OPEN_ITEM_MATERIAL), false); + menuData.setBoundMaterial(itemReader.getMaterial()); + + if (itemReader.hasExplicitDataValue()) { + menuData.setBoundDataValue(itemReader.getDataValue()); + } + } catch (FormatException e) { + errorLogger.addError("The item \""+ config.getString(Nodes.OPEN_ITEM_MATERIAL) + "\" used to open the menu \"" + config.getFileName() + "\" is invalid: " + e.getMessage()); + } + + boolean leftClick = config.getBoolean(Nodes.OPEN_ITEM_LEFT_CLICK); + boolean rightClick = config.getBoolean(Nodes.OPEN_ITEM_RIGHT_CLICK); + + if (leftClick || rightClick) { + menuData.setClickType(ClickType.fromOptions(leftClick, rightClick)); + } + } + + return menuData; + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/task/ErrorLoggerTask.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/task/ErrorLoggerTask.java new file mode 100644 index 0000000..be78ee9 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/task/ErrorLoggerTask.java @@ -0,0 +1,43 @@ +package com.gmail.filoghost.chestcommands.task; + +import java.util.List; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; + +import com.gmail.filoghost.chestcommands.ChestCommands; +import com.gmail.filoghost.chestcommands.util.ErrorLogger; +import com.google.common.base.Joiner; +import com.google.common.collect.Lists; + +public class ErrorLoggerTask implements Runnable { + + private ErrorLogger errorLogger; + + public ErrorLoggerTask(ErrorLogger errorLogger) { + this.errorLogger = errorLogger; + } + + @Override + public void run() { + + List lines = Lists.newArrayList(); + + lines.add(" "); + lines.add(ChatColor.RED + "#------------------- Chest Commands Errors -------------------#"); + int count = 1; + for (String error : errorLogger.getErrors()) { + lines.add(ChatColor.GRAY + "" + (count++) + ") " + ChatColor.WHITE + error); + } + lines.add(ChatColor.RED + "#-------------------------------------------------------------#"); + + String output = Joiner.on('\n').join(lines); + + if (ChestCommands.getSettings().use_console_colors) { + Bukkit.getConsoleSender().sendMessage(output); + } else { + System.out.println(ChatColor.stripColor(output)); + } + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/util/CaseInsensitiveMap.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/util/CaseInsensitiveMap.java new file mode 100644 index 0000000..ed404c1 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/util/CaseInsensitiveMap.java @@ -0,0 +1,49 @@ +package com.gmail.filoghost.chestcommands.util; + +import java.util.HashMap; +import java.util.Map; + +public class CaseInsensitiveMap extends HashMap { + + private static final long serialVersionUID = 4712822893326841081L; + + public static CaseInsensitiveMap create() { + return new CaseInsensitiveMap(); + } + + @Override + public V put(String key, V value) { + return super.put(key.toLowerCase(), value); + } + + @Override + public V get(Object key) { + return super.get(key.getClass() == String.class ? key.toString().toLowerCase() : key); + } + + public String getKey(V value) { + for (java.util.Map.Entry entry : entrySet()) { + if (entry.getValue().equals(value)) { + return entry.getKey(); + } + } + + return null; + } + + @Override + public boolean containsKey(Object key) { + return super.containsKey(key.getClass() == String.class ? key.toString().toLowerCase() : key); + } + + @Override + public V remove(Object key) { + return super.remove(key.getClass() == String.class ? key.toString().toLowerCase() : key); + } + + @Override + public void putAll(Map m) { + throw new UnsupportedOperationException("putAll not supported"); + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/util/ClickType.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/util/ClickType.java new file mode 100644 index 0000000..ab801a7 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/util/ClickType.java @@ -0,0 +1,33 @@ +package com.gmail.filoghost.chestcommands.util; + +import org.bukkit.event.block.Action; + +public enum ClickType { + + LEFT, + RIGHT, + BOTH; + + public static ClickType fromOptions(boolean left, boolean right) { + if (left && right) { + return BOTH; + } else if (left && !right) { + return LEFT; + } else if (!left && right) { + return RIGHT; + } else { + return null; + } + } + + public boolean isValidInteract(Action action) { + if (action == Action.LEFT_CLICK_AIR || action == Action.LEFT_CLICK_BLOCK) { + return this == LEFT || this == BOTH; + } else if (action == Action.RIGHT_CLICK_AIR || action == Action.RIGHT_CLICK_BLOCK) { + return this == RIGHT || this == BOTH; + } else { + return false; + } + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/util/ErrorLogger.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/util/ErrorLogger.java new file mode 100644 index 0000000..8d52253 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/util/ErrorLogger.java @@ -0,0 +1,29 @@ +package com.gmail.filoghost.chestcommands.util; + +import java.util.ArrayList; +import java.util.List; + +/** + * This is a class to collect all the errors found while loading the plugin. + */ +public class ErrorLogger { + + private List errors = new ArrayList(); + + public void addError(String error) { + errors.add(error); + } + + public List getErrors() { + return new ArrayList(errors); + } + + public boolean hasErrors() { + return errors.size() > 0; + } + + public int getSize() { + return errors.size(); + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/util/InventoryUtils.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/util/InventoryUtils.java new file mode 100644 index 0000000..ca942e9 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/util/InventoryUtils.java @@ -0,0 +1,38 @@ +package com.gmail.filoghost.chestcommands.util; + +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +public class InventoryUtils { + + public static boolean hasInventoryFull(Player player) { + return player.getInventory().firstEmpty() == -1; + } + + public static boolean containsAtLeast(Inventory inv, Material material, int minAmount) { + int contained = 0; + + for (ItemStack item : inv.getContents()) { + if (item != null && item.getType() == material) { + contained += item.getAmount(); + } + } + + return contained >= minAmount; + } + + public static boolean containsAtLeast(Inventory inv, Material material, int minAmount, short data) { + int contained = 0; + + for (ItemStack item : inv.getContents()) { + if (item != null && item.getType() == material && item.getDurability() == data) { + contained += item.getAmount(); + } + } + + return contained >= minAmount; + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/util/ItemStackReader.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/util/ItemStackReader.java new file mode 100644 index 0000000..5cdb94a --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/util/ItemStackReader.java @@ -0,0 +1,95 @@ +package com.gmail.filoghost.chestcommands.util; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import com.gmail.filoghost.chestcommands.exception.FormatException; + +public class ItemStackReader { + + private Material material = Material.STONE; // In the worst case (bad exception handling) we just get stone. + private int amount = 1; + private short dataValue = 0; + private boolean explicitDataValue = false; + + /** + * Reads item in the format "id:data, amount" + * id can be either the id of the material or its name. + * for example wool:5, 3 is a valid input. + */ + public ItemStackReader(String input, boolean parseAmount) throws FormatException { + Validate.notNull(input, "input cannot be null"); + + // Remove spaces, they're not needed. + input = StringUtils.stripChars(input, " _-"); + + if (parseAmount) { + // Read the optional amount. + String[] splitAmount = input.split(","); + + if (splitAmount.length > 1) { + + if (!Utils.isValidInteger(splitAmount[1])) { + throw new FormatException("invalid amount \"" + splitAmount[1] + "\""); + } + + int amount = Integer.parseInt(splitAmount[1]); + if (amount <= 0) throw new FormatException("invalid amount \"" + splitAmount[1] + "\""); + this.amount = amount; + + // Only keep the first part as input. + input = splitAmount[0]; + } + } + + + // Read the optional data value. + String[] splitByColons = input.split(":"); + + if (splitByColons.length > 1) { + + if (!Utils.isValidShort(splitByColons[1])) { + throw new FormatException("invalid data value \"" + splitByColons[1] + "\""); + } + + short dataValue = Short.parseShort(splitByColons[1]); + if (dataValue < 0 || dataValue > 15) { + throw new FormatException("invalid data value \"" + splitByColons[1] + "\""); + } + + this.explicitDataValue = true; + this.dataValue = dataValue; + + // Only keep the first part as input. + input = splitByColons[0]; + } + + Material material = Utils.matchMaterial(input); + + if (material == null || material == Material.AIR) { + throw new FormatException("invalid material \"" + input + "\""); + } + this.material = material; + } + + public Material getMaterial() { + return material; + } + + public int getAmount() { + return amount; + } + + public short getDataValue() { + return dataValue; + } + + public boolean hasExplicitDataValue() { + return explicitDataValue; + } + + public ItemStack createStack() { + return new ItemStack(material, amount, dataValue); + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/util/StringUtils.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/util/StringUtils.java new file mode 100644 index 0000000..8fc4192 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/util/StringUtils.java @@ -0,0 +1,84 @@ +package com.gmail.filoghost.chestcommands.util; + +public class StringUtils { + + public static String stripChars(String input, String removed) { + if (removed == null || removed.isEmpty()) { + return input; + } + + return stripChars(input, removed.toCharArray()); + } + + // Removes the first slash, and returns the all the chars until a space is encontered. + public static String getCleanCommand(String message) { + char[] chars = message.toCharArray(); + + if (chars.length <= 1) { + return ""; + } + + int pos = 0; + for (int i = 1; i < chars.length; i++) { + if (chars[i] == ' ') { + break; + } + + chars[(pos++)] = chars[i]; + } + + return new String(chars, 0, pos); + } + + public static String stripChars(String input, char... removed) { + if (input == null || input.isEmpty() || removed.length == 0) { + return input; + } + + char[] chars = input.toCharArray(); + + int pos = 0; + for (int i = 0; i < chars.length; i++) { + if (!arrayContains(removed, chars[i])) { + chars[(pos++)] = chars[i]; + } + } + + return new String(chars, 0, pos); + } + + private static boolean arrayContains(char[] arr, char match) { + for (char c : arr) { + if (c == match) { + return true; + } + } + + return false; + } + + public static String capitalizeFully(String input) { + if (input == null) return null; + + String s = input.toLowerCase(); + + int strLen = s.length(); + StringBuffer buffer = new StringBuffer(strLen); + boolean capitalizeNext = true; + for (int i = 0; i < strLen; i++) { + char ch = s.charAt(i); + + if (Character.isWhitespace(ch)) { + buffer.append(ch); + capitalizeNext = true; + } else if (capitalizeNext) { + buffer.append(Character.toTitleCase(ch)); + capitalizeNext = false; + } else { + buffer.append(ch); + } + } + return buffer.toString(); + } + +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/util/Utils.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/util/Utils.java new file mode 100644 index 0000000..5ce8783 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/util/Utils.java @@ -0,0 +1,261 @@ +package com.gmail.filoghost.chestcommands.util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.text.DecimalFormat; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.bukkit.ChatColor; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.plugin.Plugin; + +import com.gmail.filoghost.chestcommands.ChestCommands; +import com.gmail.filoghost.chestcommands.exception.FormatException; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +public class Utils { + + // Default material names are ugly. + private static Map materialMap = Maps.newHashMap(); + static { + for (Material mat : Material.values()) { + materialMap.put(StringUtils.stripChars(mat.toString(), "_").toLowerCase(), mat); + } + + Map tempMap = Maps.newHashMap(); + + tempMap.put("iron bar", Material.IRON_FENCE); + tempMap.put("iron bars", Material.IRON_FENCE); + tempMap.put("glass pane", Material.THIN_GLASS); + tempMap.put("nether wart", Material.NETHER_STALK); + tempMap.put("nether warts", Material.NETHER_STALK); + tempMap.put("slab", Material.STEP); + tempMap.put("double slab", Material.DOUBLE_STEP); + tempMap.put("stone brick", Material.SMOOTH_BRICK); + tempMap.put("stone bricks", Material.SMOOTH_BRICK); + tempMap.put("stone stair", Material.SMOOTH_STAIRS); + tempMap.put("stone stairs", Material.SMOOTH_STAIRS); + tempMap.put("potato", Material.POTATO_ITEM); + tempMap.put("carrot", Material.CARROT_ITEM); + tempMap.put("brewing stand", Material.BREWING_STAND_ITEM); + tempMap.put("cauldron", Material.CAULDRON_ITEM); + tempMap.put("carrot on stick", Material.CARROT_STICK); + tempMap.put("carrot on a stick", Material.CARROT_STICK); + tempMap.put("cobblestone wall", Material.COBBLE_WALL); + tempMap.put("acacia wood stairs", Material.ACACIA_STAIRS); + tempMap.put("dark oak wood stairs", Material.DARK_OAK_STAIRS); + tempMap.put("wood slab", Material.WOOD_STEP); + tempMap.put("double wood slab", Material.WOOD_DOUBLE_STEP); + tempMap.put("repeater", Material.DIODE); + tempMap.put("piston", Material.PISTON_BASE); + tempMap.put("sticky piston", Material.PISTON_STICKY_BASE); + tempMap.put("flower pot", Material.FLOWER_POT_ITEM); + tempMap.put("wood showel", Material.WOOD_SPADE); + tempMap.put("stone showel", Material.STONE_SPADE); + tempMap.put("gold showel", Material.GOLD_SPADE); + tempMap.put("iron showel", Material.IRON_SPADE); + tempMap.put("diamond showel", Material.DIAMOND_SPADE); + tempMap.put("steak", Material.COOKED_BEEF); + tempMap.put("cooked porkchop", Material.GRILLED_PORK); + tempMap.put("raw porkchop", Material.PORK); + tempMap.put("hardened clay", Material.HARD_CLAY); + tempMap.put("huge brown mushroom", Material.HUGE_MUSHROOM_1); + tempMap.put("huge red mushroom", Material.HUGE_MUSHROOM_2); + tempMap.put("mycelium", Material.MYCEL); + tempMap.put("poppy", Material.RED_ROSE); + tempMap.put("comparator", Material.REDSTONE_COMPARATOR); + tempMap.put("skull", Material.SKULL_ITEM); + tempMap.put("head", Material.SKULL_ITEM); + tempMap.put("redstone torch", Material.REDSTONE_TORCH_ON); + tempMap.put("redstone lamp", Material.REDSTONE_LAMP_OFF); + tempMap.put("glistering melon", Material.SPECKLED_MELON); +// tempMap.put("acacia leaves", Material.LEAVES_2); It wouldn't work with 1.6 :/ +// tempMap.put("acacia log", Material.LOG_2); + tempMap.put("gunpowder", Material.SULPHUR); + tempMap.put("lilypad", Material.WATER_LILY); + tempMap.put("command block", Material.COMMAND); + + for (Entry tempEntry : tempMap.entrySet()) { + materialMap.put(StringUtils.stripChars(tempEntry.getKey(), " _-").toLowerCase(), tempEntry.getValue()); + } + } + + public static String colorizeName(String input) { + if (input == null) return null; + + if (input.isEmpty()) return input; + + if (input.charAt(0) != ChatColor.COLOR_CHAR) { + return ChestCommands.getSettings().default_color__name + addColors(input); + } else { + return addColors(input); + } + } + + public static List colorizeLore(List input) { + if (input == null) return null; + for (int i = 0; i < input.size(); i++) { + + String line = input.get(i); + + if (line.isEmpty()) continue; + + if (line.charAt(0) != ChatColor.COLOR_CHAR) { + input.set(i, ChestCommands.getSettings().default_color__lore + addColors(line)); + } else { + input.set(i, addColors(line)); + } + } + return input; + } + + public static String addColors(String input) { + if (input == null) return null; + return ChatColor.translateAlternateColorCodes('&', input); + } + + public static List addColors(List input) { + if (input == null) return null; + for (int i = 0; i < input.size(); i++) { + input.set(i, addColors(input.get(i))); + } + return input; + } + + public static String addYamlExtension(String input) { + if (input == null) return null; + return input.toLowerCase().endsWith(".yml") ? input : input + ".yml"; + } + + private static DecimalFormat decimalFormat = new DecimalFormat("0.##"); + public static String decimalFormat(double number) { + return decimalFormat.format(number); + } + + @SuppressWarnings("deprecation") + public static Material matchMaterial(String input) { + if (input == null) return null; + + input = StringUtils.stripChars(input.toLowerCase(), " _-"); + + if (isValidInteger(input)) { + return Material.getMaterial(Integer.parseInt(input)); + } + + return materialMap.get(input); + } + + public static Sound matchSound(String input) { + if (input == null) return null; + + input = StringUtils.stripChars(input.toLowerCase(), " _-"); + + for (Sound sound : Sound.values()) { + if (StringUtils.stripChars(sound.toString().toLowerCase(), "_").equals(input)) return sound; + } + return null; + } + + public static String formatMaterial(Material material) { + return StringUtils.capitalizeFully(material.toString().replace("_", " ")); + } + + public static int makePositive(int i) { + return i < 0 ? 0 : i; + } + + public static boolean isValidInteger(String input) { + try { + Integer.parseInt(input); + return true; + } catch (NumberFormatException ex) { + return false; + } + } + + public static boolean isValidShort(String input) { + try { + Short.parseShort(input); + return true; + } catch (NumberFormatException ex) { + return false; + } + } + + public static boolean isValidPositiveDouble(String input) { + try { + return Double.parseDouble(input) > 0.0; + } catch (NumberFormatException ex) { + return false; + } + } + + public static List readLines(File file) throws IOException, Exception { + BufferedReader br = null; + + try { + List lines = Lists.newArrayList(); + + if (!file.exists()) { + throw new FileNotFoundException(); + } + + br = new BufferedReader(new FileReader(file)); + String line = br.readLine(); + + while (line != null) { + lines.add(line); + line = br.readLine(); + } + + return lines; + } finally { + if (br != null) { + try { + br.close(); + } catch (IOException e) { + } + } + } + } + + public static Color parseColor(String input) throws FormatException { + String[] split = StringUtils.stripChars(input, " ").split(","); + + if (split.length != 3) { + throw new FormatException("it must be in the format \"red, green, blue\"."); + } + + int red, green, blue; + + try { + red = Integer.parseInt(split[0]); + green = Integer.parseInt(split[1]); + blue = Integer.parseInt(split[2]); + } catch (NumberFormatException ex) { + throw new FormatException("it contains invalid numbers."); + } + + if (red < 0 || red > 255 || green < 0 || green > 255 || blue < 0 || blue > 255) { + throw new FormatException("it should only contain numbers between 0 and 255."); + } + + return Color.fromRGB(red, green, blue); + } + + public static void saveResourceSafe(Plugin plugin, String name) { + try { + plugin.saveResource(name, false); + } catch (Exception ex) { + // Shhh... + } + } +} diff --git a/ChestCommands/src/com/gmail/filoghost/chestcommands/util/Validate.java b/ChestCommands/src/com/gmail/filoghost/chestcommands/util/Validate.java new file mode 100644 index 0000000..b8c24d2 --- /dev/null +++ b/ChestCommands/src/com/gmail/filoghost/chestcommands/util/Validate.java @@ -0,0 +1,23 @@ +package com.gmail.filoghost.chestcommands.util; + +public class Validate { + + public static void notNull(Object object, String error) { + if (object == null) { + throw new NullPointerException(error); + } + } + + public static void isTrue(boolean statement, String error) { + if (!statement) { + throw new IllegalArgumentException(error); + } + } + + public static void isFalse(boolean statement, String error) { + if (statement) { + throw new IllegalArgumentException(error); + } + } + +}