diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java index f3ce87a2f8..7ff27f264a 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -58,6 +58,7 @@ import org.bukkit.command.SimpleCommandMap; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.serialization.ConfigurationSerialization; +import org.bukkit.craftbukkit.help.SimpleHelpMap; import org.bukkit.craftbukkit.inventory.CraftFurnaceRecipe; import org.bukkit.craftbukkit.inventory.CraftInventoryCustom; import org.bukkit.craftbukkit.inventory.CraftRecipe; @@ -81,6 +82,7 @@ import org.bukkit.event.world.WorldLoadEvent; import org.bukkit.event.world.WorldSaveEvent; import org.bukkit.event.world.WorldUnloadEvent; import org.bukkit.generator.ChunkGenerator; +import org.bukkit.help.HelpMap; import org.bukkit.inventory.FurnaceRecipe; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.Inventory; @@ -126,6 +128,7 @@ public final class CraftServer implements Server { private final ServicesManager servicesManager = new SimpleServicesManager(); private final BukkitScheduler scheduler = new CraftScheduler(); private final SimpleCommandMap commandMap = new SimpleCommandMap(this); + private final SimpleHelpMap helpMap = new SimpleHelpMap(); private final StandardMessenger messenger = new StandardMessenger(); private final PluginManager pluginManager = new SimplePluginManager(this, commandMap); protected final MinecraftServer console; @@ -215,6 +218,11 @@ public final class CraftServer implements Server { } public void enablePlugins(PluginLoadOrder type) { + if (type == PluginLoadOrder.STARTUP) { + helpMap.clear(); + helpMap.initializeGeneralTopics(this); + } + Plugin[] plugins = pluginManager.getPlugins(); for (Plugin plugin : plugins) { @@ -227,6 +235,7 @@ public final class CraftServer implements Server { commandMap.registerServerAliases(); loadCustomPermissions(); DefaultPermissions.registerCorePermissions(); + helpMap.initializeCommands(this); } } @@ -1122,4 +1131,12 @@ public final class CraftServer implements Server { Validate.isTrue(size % 9 == 0, "Chests must have a size that is a multiple of 9!"); return new CraftInventoryCustom(owner, size, title); } + + public HelpMap getHelpMap() { + return helpMap; + } + + public SimpleCommandMap getCommandMap() { + return commandMap; + } } diff --git a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java index 2f9879ff43..dbcfddc9ea 100644 --- a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java +++ b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java @@ -16,6 +16,12 @@ public class CraftConsoleCommandSender extends ServerCommandSender implements Co System.out.println(ChatColor.stripColor(message)); } + public void sendMessage(String[] messages) { + for (String message : messages) { + sendMessage(message); + } + } + public String getName() { return "CONSOLE"; } diff --git a/src/main/java/org/bukkit/craftbukkit/command/CraftRemoteConsoleCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/CraftRemoteConsoleCommandSender.java index dd6eab614a..d4b04712f5 100644 --- a/src/main/java/org/bukkit/craftbukkit/command/CraftRemoteConsoleCommandSender.java +++ b/src/main/java/org/bukkit/craftbukkit/command/CraftRemoteConsoleCommandSender.java @@ -11,6 +11,12 @@ public class CraftRemoteConsoleCommandSender extends ServerCommandSender impleme public void sendMessage(String message) { RemoteControlCommandListener.instance.sendMessage(message + "\n"); // Send a newline after each message, to preserve formatting. } + + public void sendMessage(String[] messages) { + for (String message : messages) { + sendMessage(message); + } + } public String getName() { return "Rcon"; diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java index dcf5a3ce58..b065d47dfd 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -135,6 +135,12 @@ public class CraftPlayer extends CraftHumanEntity implements Player { public void sendMessage(String message) { this.sendRawMessage(message); } + + public void sendMessage(String[] messages) { + for (String message : messages) { + sendMessage(message); + } + } public String getDisplayName() { return getHandle().displayName; diff --git a/src/main/java/org/bukkit/craftbukkit/help/CustomHelpTopic.java b/src/main/java/org/bukkit/craftbukkit/help/CustomHelpTopic.java new file mode 100644 index 0000000000..058f6ca6f8 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/help/CustomHelpTopic.java @@ -0,0 +1,32 @@ +package org.bukkit.craftbukkit.help; + +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.help.HelpTopic; + +/** + * This is a help topic implementation for general topics registered in the help.yml file. + */ +public class CustomHelpTopic extends HelpTopic { + + private String permissionNode; + + public CustomHelpTopic(String name, String shortText, String fullText, String permissionNode) { + this.permissionNode = permissionNode; + this.name = name; + this.shortText = shortText; + this.fullText = shortText + "\n" + fullText; + } + + public boolean canSee(CommandSender sender) { + if (sender instanceof ConsoleCommandSender) { + return true; + } + + if (!permissionNode.equals("")) { + return sender.hasPermission(permissionNode); + } else { + return true; + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/help/DefaultHelpTopic.java b/src/main/java/org/bukkit/craftbukkit/help/DefaultHelpTopic.java new file mode 100644 index 0000000000..64ba60a7bd --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/help/DefaultHelpTopic.java @@ -0,0 +1,57 @@ +package org.bukkit.craftbukkit.help; + +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.help.HelpTopic; +import org.bukkit.util.ChatPaginator; + +import java.util.Collection; + +/** + * This help topic generates the list of all other help topics. + */ +public class DefaultHelpTopic extends HelpTopic { + + private Collection allTopics; + + public DefaultHelpTopic(Collection allTopics) { + this.allTopics = allTopics; + } + + public boolean canSee(CommandSender sender) { + return true; + } + + public String getName() { + return "Overall"; + } + + public String getShortText() { + return ""; + } + + public String getFullText(CommandSender sender) { + StringBuilder sb = new StringBuilder(); + for (HelpTopic topic : allTopics) { + if (topic.canSee(sender)) { + StringBuilder line = new StringBuilder(); + line.append(ChatColor.GOLD); + line.append(topic.getName()); + line.append(": "); + line.append(ChatColor.WHITE); + line.append(topic.getShortText()); + + String lineStr = line.toString().replace("\n", ". "); + if (sender instanceof Player && lineStr.length() > ChatPaginator.AVERAGE_CHAT_PAGE_WIDTH) { + sb.append(lineStr.substring(0, ChatPaginator.AVERAGE_CHAT_PAGE_WIDTH - 3)); + sb.append("..."); + } else { + sb.append(lineStr); + } + sb.append("\n"); + } + } + return sb.toString(); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/help/GenericCommandHelpTopic.java b/src/main/java/org/bukkit/craftbukkit/help/GenericCommandHelpTopic.java new file mode 100644 index 0000000000..05915d4f37 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/help/GenericCommandHelpTopic.java @@ -0,0 +1,70 @@ +package org.bukkit.craftbukkit.help; + +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.apache.commons.lang.StringUtils; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.command.PluginCommand; +import org.bukkit.command.defaults.VanillaCommand; +import org.bukkit.help.HelpTopic; + +public class GenericCommandHelpTopic extends HelpTopic { + + private Command command; + + public GenericCommandHelpTopic(Command command) { + this.command = command; + + if (command.getLabel().startsWith("/")) { + name = command.getLabel(); + } else { + name = "/" + command.getLabel(); + } + + // The short text is the first line of the description + int i = command.getDescription().indexOf("\n"); + if (i > 1) { + shortText = command.getDescription().substring(0, i - 1); + } else { + shortText = command.getDescription(); + } + + // Build full text + StringBuffer sb = new StringBuffer(); + + sb.append(ChatColor.GOLD); + sb.append("Description: "); + sb.append(ChatColor.WHITE); + sb.append(command.getDescription()); + + sb.append("\n"); + + sb.append(ChatColor.GOLD); + sb.append("Usage: "); + sb.append(ChatColor.WHITE); + sb.append(command.getUsage().replace("", name.substring(1))); + + if (command.getAliases().size() > 0) { + sb.append("\n"); + sb.append(ChatColor.GOLD); + sb.append("Aliases: "); + sb.append(ChatColor.WHITE); + sb.append(ChatColor.WHITE + StringUtils.join(command.getAliases(), ", ")); + } + fullText = sb.toString(); + } + + public boolean canSee(CommandSender sender) { + if (!command.isRegistered() && !(command instanceof VanillaCommand)) { + // Unregistered commands should not show up in the help (ignore VanillaCommands) + return false; + } + + if (sender instanceof ConsoleCommandSender) { + return true; + } + + return command.testPermissionSilent(sender); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/help/HelpTopicAmendment.java b/src/main/java/org/bukkit/craftbukkit/help/HelpTopicAmendment.java new file mode 100644 index 0000000000..f18e7afc2f --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/help/HelpTopicAmendment.java @@ -0,0 +1,40 @@ +package org.bukkit.craftbukkit.help; + +/** + * A HelpTopicAmendment represents the contents of a topic amendment from the help.yml + */ +public class HelpTopicAmendment { + private String topicName; + private String shortText; + private String fullText; + + public HelpTopicAmendment(String topicName, String shortText, String fullText) { + this.fullText = fullText; + this.shortText = shortText; + this.topicName = topicName; + } + + /** + * Gets the amended full text + * @return the full text + */ + public String getFullText() { + return fullText; + } + + /** + * Gets the amended short text + * @return the short text + */ + public String getShortText() { + return shortText; + } + + /** + * Gets the name of the topic being amended + * @return the topic name + */ + public String getTopicName() { + return topicName; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/help/HelpTopicComparator.java b/src/main/java/org/bukkit/craftbukkit/help/HelpTopicComparator.java new file mode 100644 index 0000000000..7819bef97b --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/help/HelpTopicComparator.java @@ -0,0 +1,19 @@ +package org.bukkit.craftbukkit.help; + +import java.util.Comparator; + +/** + * Used to impose a custom total ordering on help topics. All topics are listed in alphabetic order, but topics + * that start with a slash come after topics that don't. + */ +public class HelpTopicComparator implements Comparator { + public int compare(String lhs, String rhs) { + if (lhs.startsWith("/") && !rhs.startsWith("/")) { + return 1; + } else if (!lhs.startsWith("/") && rhs.startsWith("/")) { + return -1; + } else { + return lhs.compareToIgnoreCase(rhs); + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/help/HelpYamlReader.java b/src/main/java/org/bukkit/craftbukkit/help/HelpYamlReader.java new file mode 100644 index 0000000000..dd5c3966e0 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/help/HelpYamlReader.java @@ -0,0 +1,72 @@ +package org.bukkit.craftbukkit.help; + +import org.bukkit.Server; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.help.HelpTopic; + +import java.io.File; +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; +import java.util.logging.Level; + +/** + * HelpYamlReader is responsible for processing the contents of the help.yml file. + */ +public class HelpYamlReader { + + private YamlConfiguration helpYaml; + + public HelpYamlReader(Server server) { + File helpYamlFile = new File("help.yml"); + + helpYaml = YamlConfiguration.loadConfiguration(helpYamlFile); + helpYaml.options().copyDefaults(true); + helpYaml.setDefaults(YamlConfiguration.loadConfiguration(getClass().getClassLoader().getResourceAsStream("configurations/help.yml"))); + try { + if (!helpYamlFile.exists()) { + helpYaml.save(helpYamlFile); + } + } catch (IOException ex) { + server.getLogger().log(Level.SEVERE, "Could not save " + helpYamlFile, ex); + } + } + + /** + * Extracts a list of all general help topics from help.yml + * @return A list of general topics. + */ + public List getGeneralTopics() { + List topics = new LinkedList(); + ConfigurationSection generalTopics = helpYaml.getConfigurationSection("general-topics"); + if (generalTopics != null) { + for (String topicName : generalTopics.getKeys(false)) { + ConfigurationSection section = generalTopics.getConfigurationSection(topicName); + String shortText = section.getString("shortText"); + String fullText = section.getString("fullText"); + String permission = section.getString("permission"); + topics.add(new CustomHelpTopic(topicName, shortText, fullText, permission)); + } + } + return topics; + } + + /** + * Extracts a list of topic amendments from help.yml + * @return A list of amendments + */ + public List getTopicAmendments() { + List amendments = new LinkedList(); + ConfigurationSection commandTopics = helpYaml.getConfigurationSection("amended-topics"); + if (commandTopics != null) { + for (String topicName : commandTopics.getKeys(false)) { + ConfigurationSection section = commandTopics.getConfigurationSection(topicName); + String description = section.getString("shortText"); + String usage = section.getString("fullText"); + amendments.add(new HelpTopicAmendment(topicName, description, usage)); + } + } + return amendments; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/help/MultipleCommandAliasHelpTopic.java b/src/main/java/org/bukkit/craftbukkit/help/MultipleCommandAliasHelpTopic.java new file mode 100644 index 0000000000..ab18d0e78f --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/help/MultipleCommandAliasHelpTopic.java @@ -0,0 +1,50 @@ +package org.bukkit.craftbukkit.help; + +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.command.MultipleCommandAlias; +import org.bukkit.help.HelpTopic; + +/** + * This is a help topic implementation for {@link MultipleCommandAlias} commands. + */ +public class MultipleCommandAliasHelpTopic extends HelpTopic { + + private MultipleCommandAlias alias; + + public MultipleCommandAliasHelpTopic(MultipleCommandAlias alias) { + this.alias = alias; + + name = "/" + alias.getLabel(); + + // Build short text + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < alias.getCommands().length; i++) { + if (i != 0) { + sb.append(ChatColor.GOLD + " > " + ChatColor.WHITE); + } + sb.append("/"); + sb.append(alias.getCommands()[i].getLabel()); + } + shortText = sb.toString(); + + // Build full text + fullText = ChatColor.GOLD + "Alias for: " + ChatColor.WHITE + getShortText(); + } + + public boolean canSee(CommandSender sender) { + if (sender instanceof ConsoleCommandSender) { + return true; + } + + for (Command command : alias.getCommands()) { + if (!command.testPermissionSilent(sender)) { + return false; + } + } + + return true; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/help/MultipleCommandAliasHelpTopicFactory.java b/src/main/java/org/bukkit/craftbukkit/help/MultipleCommandAliasHelpTopicFactory.java new file mode 100644 index 0000000000..36ddc97683 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/help/MultipleCommandAliasHelpTopicFactory.java @@ -0,0 +1,15 @@ +package org.bukkit.craftbukkit.help; + +import org.bukkit.command.MultipleCommandAlias; +import org.bukkit.help.HelpTopic; +import org.bukkit.help.HelpTopicFactory; + +/** + * This class creates {@link MultipleCommandAliasHelpTopic} help topics from {@link MultipleCommandAlias} commands. + */ +public class MultipleCommandAliasHelpTopicFactory implements HelpTopicFactory { + + public HelpTopic createTopic(MultipleCommandAlias multipleCommandAlias) { + return new MultipleCommandAliasHelpTopic(multipleCommandAlias); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/help/SimpleHelpMap.java b/src/main/java/org/bukkit/craftbukkit/help/SimpleHelpMap.java new file mode 100644 index 0000000000..a8424aab3f --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/help/SimpleHelpMap.java @@ -0,0 +1,102 @@ +package org.bukkit.craftbukkit.help; + +import org.bukkit.command.Command; +import org.bukkit.command.MultipleCommandAlias; +import org.bukkit.command.defaults.VanillaCommand; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.help.HelpMap; +import org.bukkit.help.HelpTopic; +import org.bukkit.help.HelpTopicFactory; + +import java.util.*; + +/** + * Standard implementation of {@link HelpMap} for CraftBukkit servers. + */ +public class SimpleHelpMap implements HelpMap { + + private HelpTopic defaultTopic; + private Map helpTopics; + private Map topicFactoryMap; + + public SimpleHelpMap() { + helpTopics = new TreeMap(new HelpTopicComparator()); // Using a TreeMap for its explicit sorting on key + defaultTopic = new DefaultHelpTopic(helpTopics.values()); + topicFactoryMap = new HashMap(); + + registerHelpTopicFactory(MultipleCommandAlias.class, new MultipleCommandAliasHelpTopicFactory()); + } + + public synchronized HelpTopic getHelpTopic(String topicName) { + if (topicName.equals("")) { + return defaultTopic; + } + + if (helpTopics.containsKey(topicName)) { + return helpTopics.get(topicName); + } + + return null; + } + + public synchronized void addTopic(HelpTopic topic) { + // Existing topics take priority + if (!helpTopics.containsKey(topic.getName())) { + helpTopics.put(topic.getName(), topic); + } + } + + public synchronized void clear() { + helpTopics.clear(); + } + + /** + * Reads the general topics from help.yml and adds them to the help index. + * @param server A reference to the server. + */ + public synchronized void initializeGeneralTopics(CraftServer server) { + HelpYamlReader reader = new HelpYamlReader(server); + + // Initialize general help topics from the help.yml file + for (HelpTopic topic : reader.getGeneralTopics()) { + addTopic(topic); + } + } + + /** + * Processes all the commands registered in the server and creates help topics for them. + * @param server A reference to the server. + */ + public synchronized void initializeCommands(CraftServer server) { + // ** Load topics from highest to lowest priority order ** + + // Initialize help topics from the server's command map + for (Command command : server.getCommandMap().getCommands()) { + if (topicFactoryMap.containsKey(command.getClass())) { + addTopic(topicFactoryMap.get(command.getClass()).createTopic(command)); + } else { + addTopic(new GenericCommandHelpTopic(command)); + } + } + + // Initialize help topics from the server's fallback commands + for (VanillaCommand command : server.getCommandMap().getFallbackCommands()) { + addTopic(new GenericCommandHelpTopic(command)); + } + + // Amend help topics from the help.yml file + HelpYamlReader reader = new HelpYamlReader(server); + for (HelpTopicAmendment amendment : reader.getTopicAmendments()) { + if (helpTopics.containsKey(amendment.getTopicName())) { + helpTopics.get(amendment.getTopicName()).amendTopic(amendment.getShortText(), amendment.getFullText()); + } + } + } + + public void registerHelpTopicFactory(Class commandClass, HelpTopicFactory factory) { + if (!Command.class.isAssignableFrom(commandClass)) { + throw new IllegalArgumentException("commandClass must implement Command"); + } + topicFactoryMap.put(commandClass, factory); + } +} diff --git a/src/main/resources/configurations/bukkit.yml b/src/main/resources/configurations/bukkit.yml index 2a93eb9db9..3665b925b0 100644 --- a/src/main/resources/configurations/bukkit.yml +++ b/src/main/resources/configurations/bukkit.yml @@ -32,8 +32,8 @@ auto-updater: preferred-channel: rb host: dl.bukkit.org aliases: - icanhasbukkit: - - version +# icanhasbukkit: +# - version database: username: bukkit isolation: SERIALIZABLE diff --git a/src/main/resources/configurations/help.yml b/src/main/resources/configurations/help.yml new file mode 100644 index 0000000000..6716d66294 --- /dev/null +++ b/src/main/resources/configurations/help.yml @@ -0,0 +1,22 @@ +# This is the help configuration file for Bukkit. +# By default you do not need to modify this file. Help topics for all plugin commands are automatically provided by +# or extracted from your installed plugins. You only need to modify this file if you wish to add new help pages to +# your server or override the help pages of existing plugin commands. +# -- +# This file is divided up into two parts: general-topics and command-topics respectively. Examples are given below. +# Color codes are allowed. When amending command topic, the string will be replaced wit the existing value +# in the help topic. +# -- +# general-topics: +# rules: +# shortText: Rules of the server +# fullText: | +# 1. Be kind to your fellow players. +# 2. No griefing. +# 3. No swearing. +# permission: topics.rules +# -- +# amended-topics: +# /stop: +# shortText: Stops the server cold....in its tracks! +# fullText: - This kills the server.