package com.laytonsmith.core.functions; import com.laytonsmith.PureUtilities.Version; import com.laytonsmith.abstraction.Implementation; import com.laytonsmith.abstraction.MCCommand; import com.laytonsmith.abstraction.MCCommandMap; import com.laytonsmith.abstraction.MCServer; import com.laytonsmith.abstraction.StaticLayer; import com.laytonsmith.annotations.api; import com.laytonsmith.core.CHVersion; import com.laytonsmith.core.Static; import com.laytonsmith.core.constructs.CArray; import com.laytonsmith.core.constructs.CBoolean; import com.laytonsmith.core.constructs.CClosure; import com.laytonsmith.core.constructs.CNull; import com.laytonsmith.core.constructs.CString; import com.laytonsmith.core.constructs.CVoid; import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.environments.Environment; import com.laytonsmith.core.exceptions.CRE.CREFormatException; import com.laytonsmith.core.exceptions.CRE.CRENotFoundException; import com.laytonsmith.core.exceptions.CRE.CREThrowable; import com.laytonsmith.core.exceptions.ConfigCompileException; import com.laytonsmith.core.exceptions.ConfigRuntimeException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; /** * * @author jb_aero */ public class Commands { public static String docs() { return "A series of functions for creating and managing custom commands."; } public static Map<String, CClosure> onCommand = new HashMap<String, CClosure>(); public static Map<String, CClosure> onTabComplete = new HashMap<String, CClosure>(); @api public static class set_tabcompleter extends AbstractFunction { @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CREFormatException.class, CRENotFoundException.class}; } @Override public boolean isRestricted() { return true; } @Override public Boolean runAsync() { return false; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { MCServer s = Static.getServer(); MCCommandMap map = s.getCommandMap(); if (map == null) { throw new CRENotFoundException(this.getName() + " is not supported in this mode (CommandMap not found).", t); } MCCommand cmd = map.getCommand(args[0].val()); if (cmd == null) { throw new CRENotFoundException("Command not found, did you forget to register it?", t); } customExec(t, environment, cmd, args[1]); return CVoid.VOID; } /** * For setting the completion code of a command that exists but might not be registered yet * @param t * @param environment * @param cmd * @param arg */ public static void customExec(Target t, Environment environment, MCCommand cmd, Construct arg) { if (arg instanceof CClosure) { onTabComplete.remove(cmd.getName()); onTabComplete.put(cmd.getName(), (CClosure) arg); } else { throw new CREFormatException("At this time, only closures are accepted as tabcompleters", t); } } @Override public String getName() { return "set_tabcompleter"; } @Override public Integer[] numArgs() { return new Integer[]{2}; } @Override public String docs() { return "void {commandname, closure} Sets the code that will be run when a user attempts" + " to tabcomplete a command. The closure is expected to return an array of completions," + " otherwise the tab_complete_command event will be fired and used to send completions." + " The closure is passed the following information in this order:" + " alias used, name of the sender, array of arguments used, array of command info."; } @Override public Version since() { return CHVersion.V3_3_1; } } @api public static class unregister_command extends AbstractFunction { @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CRENotFoundException.class}; } @Override public boolean isRestricted() { return true; } @Override public Boolean runAsync() { return false; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { MCCommandMap map = Static.getServer().getCommandMap(); if (map == null) { throw new CRENotFoundException(this.getName() + " is not supported in this mode (CommandMap not found).", t); } MCCommand cmd = map.getCommand(args[0].val()); if (cmd == null) { throw new CRENotFoundException("Command not found, did you forget to register it?", t); } return CBoolean.get(map.unregister(cmd)); } @Override public String getName() { return "unregister_command"; } @Override public Integer[] numArgs() { return new Integer[]{1}; } @Override public String docs() { return "boolean {commandname} unregisters a command from the server's command list"; } @Override public Version since() { return CHVersion.V3_3_1; } } @api public static class register_command extends AbstractFunction { @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CREFormatException.class, CRENotFoundException.class}; } @Override public boolean isRestricted() { return true; } @Override public Boolean runAsync() { return false; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { MCCommandMap map = Static.getServer().getCommandMap(); if (map == null) { throw new CRENotFoundException(this.getName() + " is not supported in this mode (CommandMap not found).", t); } MCCommand cmd = map.getCommand(args[0].val().toLowerCase()); boolean isnew = false; if (cmd == null) { isnew = true; cmd = StaticLayer.GetConvertor().getNewCommand(args[0].val().toLowerCase()); } if (args[1] instanceof CArray) { CArray ops = (CArray) args[1]; if (ops.containsKey("permission")) { cmd.setPermission(ops.get("permission", t).val()); } if (ops.containsKey("description")) { cmd.setDescription(ops.get("description", t).val()); } if (ops.containsKey("usage")) { cmd.setUsage(ops.get("usage", t).val()); } if (ops.containsKey("noPermMsg")) { cmd.setPermissionMessage(ops.get("noPermMsg", t).val()); } if (ops.containsKey("aliases")) { if (ops.get("aliases", t) instanceof CArray) { List<Construct> ca = ((CArray) ops.get("aliases", t)).asList(); List<String> aliases = new ArrayList<String>(); for (Construct c : ca) { aliases.add(c.val()); } cmd.setAliases(aliases); } } if (ops.containsKey("executor")) { set_executor.customExec(t, environment, cmd, ops.get("executor", t)); } if (ops.containsKey("tabcompleter")) { set_tabcompleter.customExec(t, environment, cmd, ops.get("tabcompleter", t)); } boolean success = true; if (isnew) { success = map.register(Implementation.GetServerType().getBranding(), cmd); } return CBoolean.get(success); } else { throw new CREFormatException("Arg 2 was expected to be an array.", t); } } @Override public String getName() { return "register_command"; } @Override public Integer[] numArgs() { return new Integer[]{2}; } @Override public String docs() { return "boolean {commandname, optionsArray} Registers a command to the server's command list," + " or updates an existing one. Options is an associative array that can have the following keys:" + " description, usage, permission, noPermMsg, aliases, tabcompleter, and/or executor." + " Everything is optional and can be modified later, except for 'aliases' due to how" + " Bukkit's command map works. 'noPermMsg' is the message displayed when the user doesn't" + " have the permission specified in 'permission'. 'Usage' is the message shown when the" + " 'executor' returns false. 'Executor' is the closure run when the command is executed," + " and can return true or false (by default is treated as true). 'tabcompleter' is the closure" + " run when a user hits tab while the command is entered and ready for args." + " It is meant to return an array of completions, but if not the tab_complete_command event" + " will be fired, and the completions of that event will be sent to the user. Both executor" + " and tabcompleter closures are passed the following information in this order:" + " alias used, name of the sender, array of arguments used, array of command info."; } @Override public Version since() { return CHVersion.V3_3_1; } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Register the /hug <player> command.", "register_command('hug', array(\n" + "\t'description': 'Spread the love!',\n" + "\t'usage': '/hug <player>',\n" + "\t'permission': 'perms.hugs',\n" + "\t'noPermMsg': 'You do not have permission to give hugs to players (Sorry :o).',\n" + "\t'tabcompleter': closure(@alias, @sender, @args) {\n" + "\t\t\tif(array_size(@args) == 0) {\n" + "\t\t\t\treturn(all_players());\n" + "\t\t\t}\n" + "\t\t\t@search = @args[array_size(@args) - 1];\n" + "\t\t\treturn(array_filter(all_players(), closure(@index, @player) {\n" + "\t\t\t\treturn(equals_ic(@search, substr(@player, 0, length(@search))));\n" + "\t\t\t}));\n" + "\t\t},\n" + "\t'aliases':array('hugg', 'hugs'),\n" + "\t'executor': closure(@alias, @sender, @args) {\n" + "\t\t\tif(array_size(@args) == 1) {\n" + "\t\t\t\tif(ponline(@args[0])) {\n" + "\t\t\t\t\tbroadcast(colorize('&4'.@sender.' &6hugs &4'.@args[0]));\n" + "\t\t\t\t} else {\n" + "\t\t\t\t\ttmsg(@sender, colorize('&cThe given player is not online.'));\n" + "\t\t\t\t}\n" + "\t\t\t\treturn(true);\n" + "\t\t\t}\n" + "\t\t\treturn(false);\n" + "\t\t}\n" + "));", "Registers the /hug command.") }; } } @api public static class set_executor extends AbstractFunction { @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CREFormatException.class, CRENotFoundException.class}; } @Override public boolean isRestricted() { return true; } @Override public Boolean runAsync() { return false; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { MCCommandMap map = Static.getServer().getCommandMap(); if (map == null) { throw new CRENotFoundException(this.getName() + " is not supported in this mode (CommandMap not found).", t); } MCCommand cmd = map.getCommand(args[0].val()); if (cmd == null) { throw new CRENotFoundException("Command not found did you forget to register it?", t); } customExec(t, environment, cmd, args[1]); return CVoid.VOID; } /** * For setting the execution code of a command that exists but might not be registered yet * @param t * @param environment * @param cmd * @param arg */ public static void customExec(Target t, Environment environment, MCCommand cmd, Construct arg) { if (arg instanceof CClosure) { onCommand.remove(cmd.getName()); onCommand.put(cmd.getName(), (CClosure) arg); } else { throw new CREFormatException("At this time, only closures are accepted as command executors.", t); } } @Override public String getName() { return "set_executor"; } @Override public Integer[] numArgs() { return new Integer[]{2}; } @Override public String docs() { return "void {commandname, closure} Sets the code that will be run when a user attempts to execute a command." + " The closure can return true false (treated as true by default). Returning false will display" + " The usage message if it is set. The closure is passed the following information in this order:" + " alias used, name of the sender, array of arguments used, array of command info."; } @Override public Version since() { return CHVersion.V3_3_1; } } @api public static class get_commands extends AbstractFunction { @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{}; } @Override public boolean isRestricted() { return true; } @Override public Boolean runAsync() { return false; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { MCCommandMap map = Static.getServer().getCommandMap(); if (map == null) { return CNull.NULL; } Collection<MCCommand> commands = map.getCommands(); CArray ret = CArray.GetAssociativeArray(t); for(MCCommand command : commands) { CArray ca = CArray.GetAssociativeArray(t); ca.set("name", new CString(command.getName(), t), t); ca.set("description", new CString(command.getDescription(), t), t); Construct permission; if (command.getPermission() == null) { permission = CNull.NULL; } else { permission = new CString(command.getPermission(), t); } ca.set("permission", permission, t); ca.set("nopermmsg", new CString(command.getPermissionMessage(), t), t); ca.set("usage", new CString(command.getUsage(), t), t); CArray aliases = new CArray(t); for (String a : command.getAliases()) { aliases.push(new CString(a, t), t); } ca.set("aliases", aliases, t); ret.set(command.getName(), ca, t); } return ret; } @Override public String getName() { return "get_commands"; } @Override public Integer[] numArgs() { return new Integer[]{0}; } @Override public String docs() { return "array {} Returns an array of command arrays in the format register_command expects or null if no commands could be found." + " This does not include " + Implementation.GetServerType().getBranding() + " aliases, as they are not registered commands."; } @Override public Version since() { return CHVersion.V3_3_1; } } @api public static class clear_commands extends AbstractFunction { @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{}; } @Override public boolean isRestricted() { return true; } @Override public Boolean runAsync() { return false; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { MCCommandMap map = Static.getServer().getCommandMap(); if (map != null) { map.clearCommands(); } return CVoid.VOID; } @Override public String getName() { return "clear_commands"; } @Override public Integer[] numArgs() { return new Integer[]{0}; } @Override public String docs() { return "void {} Attempts to clear all registered commands on the server. Note that this probably has some special" + " limitations, but they are a bit unclear as to what commands can and cannot be unregistered."; } @Override public Version since() { return CHVersion.V3_3_1; } } }