Package ch.njol.skript.command

Source Code of ch.njol.skript.command.Commands

/*
*   This file is part of Skript.
*
*  Skript is free software: you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation, either version 3 of the License, or
*  (at your option) any later version.
*
*  Skript is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with Skript.  If not, see <http://www.gnu.org/licenses/>.
*
*
* Copyright 2011-2014 Peter Güttinger
*
*/

package ch.njol.skript.command;

import java.io.File;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.logging.Filter;
import java.util.logging.LogRecord;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.command.SimpleCommandMap;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.event.player.PlayerChatEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.server.ServerCommandEvent;
import org.bukkit.help.HelpMap;
import org.bukkit.help.HelpTopic;
import org.bukkit.plugin.SimplePluginManager;
import org.eclipse.jdt.annotation.Nullable;

import ch.njol.skript.ScriptLoader;
import ch.njol.skript.Skript;
import ch.njol.skript.SkriptConfig;
import ch.njol.skript.classes.ClassInfo;
import ch.njol.skript.classes.Parser;
import ch.njol.skript.config.SectionNode;
import ch.njol.skript.config.validate.SectionValidator;
import ch.njol.skript.lang.Effect;
import ch.njol.skript.lang.ParseContext;
import ch.njol.skript.lang.SkriptParser;
import ch.njol.skript.localization.ArgsMessage;
import ch.njol.skript.localization.Language;
import ch.njol.skript.localization.Message;
import ch.njol.skript.log.BukkitLoggerFilter;
import ch.njol.skript.log.RetainingLogHandler;
import ch.njol.skript.log.SkriptLogger;
import ch.njol.skript.registrations.Classes;
import ch.njol.skript.util.Utils;
import ch.njol.util.Callback;
import ch.njol.util.NonNullPair;
import ch.njol.util.StringUtils;

/**
* @author Peter Güttinger
*/
@SuppressWarnings("deprecation")
public abstract class Commands {
 
  public final static ArgsMessage m_too_many_arguments = new ArgsMessage("commands.too many arguments");
  public final static Message m_correct_usage = new Message("commands.correct usage");
  public final static Message m_internal_error = new Message("commands.internal error");
 
  private final static Map<String, ScriptCommand> commands = new HashMap<String, ScriptCommand>();
 
  @Nullable
  private static SimpleCommandMap commandMap = null;
  @Nullable
  private static Map<String, Command> cmKnownCommands;
  @Nullable
  private static Set<String> cmAliases;
  static {
    try {
      if (Bukkit.getPluginManager() instanceof SimplePluginManager) {
        final Field commandMapField = SimplePluginManager.class.getDeclaredField("commandMap");
        commandMapField.setAccessible(true);
        commandMap = (SimpleCommandMap) commandMapField.get(Bukkit.getPluginManager());
       
        final Field knownCommandsField = SimpleCommandMap.class.getDeclaredField("knownCommands");
        knownCommandsField.setAccessible(true);
        cmKnownCommands = (Map<String, Command>) knownCommandsField.get(commandMap);
       
        try {
          final Field aliasesField = SimpleCommandMap.class.getDeclaredField("aliases");
          aliasesField.setAccessible(true);
          cmAliases = (Set<String>) aliasesField.get(commandMap);
        } catch (final NoSuchFieldException e) {}
      }
    } catch (final SecurityException e) {
      Skript.error("Please disable the security manager");
      commandMap = null;
    } catch (final Exception e) {
      Skript.outdatedError(e);
      commandMap = null;
    }
  }
 
  private final static SectionValidator commandStructure = new SectionValidator()
      .addEntry("usage", true)
      .addEntry("description", true)
      .addEntry("permission", true)
      .addEntry("permission message", true)
      .addEntry("aliases", true)
      .addEntry("executable by", true)
      .addSection("trigger", false);
 
  @Nullable
  public static List<Argument<?>> currentArguments = null;
 
  @SuppressWarnings("null")
  private final static Pattern escape = Pattern.compile("[" + Pattern.quote("(|)<>%\\") + "]");
  @SuppressWarnings("null")
  private final static Pattern unescape = Pattern.compile("\\\\[" + Pattern.quote("(|)<>%\\") + "]");
 
  private final static String escape(final String s) {
    return "" + escape.matcher(s).replaceAll("\\\\$0");
  }
 
  private final static String unescape(final String s) {
    return "" + unescape.matcher(s).replaceAll("$0");
  }
 
  private final static Listener commandListener = new Listener() {
    @SuppressWarnings("null")
    @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
    public void onPlayerCommand(final PlayerCommandPreprocessEvent e) {
      if (handleCommand(e.getPlayer(), e.getMessage().substring(1)))
        e.setCancelled(true);
    }
   
    @SuppressWarnings("null")
    @EventHandler(priority = EventPriority.HIGHEST)
    public void onServerCommand(final ServerCommandEvent e) {
      if (e.getCommand() == null || e.getCommand().isEmpty())
        return;
      if (SkriptConfig.enableEffectCommands.value() && e.getCommand().startsWith(SkriptConfig.effectCommandToken.value())) {
        if (handleEffectCommand(e.getSender(), e.getCommand())) {
          e.setCommand("");
          suppressUnknownCommandMessage = true;
        }
        return;
      }
      if (handleCommand(e.getSender(), e.getCommand())) {
        e.setCommand("");
        suppressUnknownCommandMessage = true;
      }
    }
  };
 
  static boolean suppressUnknownCommandMessage = false;
  static {
    BukkitLoggerFilter.addFilter(new Filter() {
      @Override
      public boolean isLoggable(final @Nullable LogRecord record) {
        if (record == null)
          return false;
        if (suppressUnknownCommandMessage && record.getMessage() != null && record.getMessage().toLowerCase().startsWith("unknown command")) {
          suppressUnknownCommandMessage = false;
          return false;
        }
        return true;
      }
    });
  }
 
  @Nullable
  private final static Listener pre1_3chatListener = Skript.isRunningMinecraft(1, 3) ? null : new Listener() {
    @SuppressWarnings("null")
    @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
    public void onPlayerChat(final PlayerChatEvent e) {
      if (!SkriptConfig.enableEffectCommands.value() || !e.getMessage().startsWith(SkriptConfig.effectCommandToken.value()))
        return;
      if (handleEffectCommand(e.getPlayer(), e.getMessage()))
        e.setCancelled(true);
    }
  };
  @Nullable
  private final static Listener post1_3chatListener = !Skript.isRunningMinecraft(1, 3) ? null : new Listener() {
    @SuppressWarnings("null")
    @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
    public void onPlayerChat(final AsyncPlayerChatEvent e) {
      if (!SkriptConfig.enableEffectCommands.value() || !e.getMessage().startsWith(SkriptConfig.effectCommandToken.value()))
        return;
      if (!e.isAsynchronous()) {
        if (handleEffectCommand(e.getPlayer(), e.getMessage()))
          e.setCancelled(true);
      } else {
        final Future<Boolean> f = Bukkit.getScheduler().callSyncMethod(Skript.getInstance(), new Callable<Boolean>() {
          @Override
          public Boolean call() throws Exception {
            return handleEffectCommand(e.getPlayer(), e.getMessage());
          }
        });
        try {
          while (true) {
            try {
              if (f.get())
                e.setCancelled(true);
              break;
            } catch (final InterruptedException e1) {}
          }
        } catch (final ExecutionException e1) {
          Skript.exception(e1);
        }
      }
    }
  };
 
  /**
   * @param sender
   * @param command full command string without the slash
   * @return whether to cancel the event
   */
  final static boolean handleCommand(final CommandSender sender, final String command) {
    final String[] cmd = command.split("\\s+", 2);
    cmd[0] = cmd[0].toLowerCase();
    if (cmd[0].endsWith("?")) {
      final ScriptCommand c = commands.get(cmd[0].substring(0, cmd[0].length() - 1));
      if (c != null) {
        c.sendHelp(sender);
        return true;
      }
    }
    final ScriptCommand c = commands.get(cmd[0]);
    if (c != null) {
//      if (cmd.length == 2 && cmd[1].equals("?")) {
//        c.sendHelp(sender);
//        return true;
//      }
      if (SkriptConfig.logPlayerCommands.value() && !(sender instanceof ConsoleCommandSender))
        SkriptLogger.LOGGER.info(sender.getName() + ": /" + command);
      c.execute(sender, "" + cmd[0], cmd.length == 1 ? "" : "" + cmd[1]);
      return true;
    }
    return false;
  }
 
  @SuppressWarnings("unchecked")
  final static boolean handleEffectCommand(final CommandSender sender, String command) {
    if (!(sender instanceof ConsoleCommandSender || sender.hasPermission("skript.effectcommands") || SkriptConfig.allowOpsToUseEffectCommands.value() && sender.isOp()))
      return false;
    final boolean wasLocal = Language.setUseLocal(false);
    try {
      command = "" + command.substring(SkriptConfig.effectCommandToken.value().length()).trim();
      final RetainingLogHandler log = SkriptLogger.startRetainingLog();
      try {
        ScriptLoader.setCurrentEvent("effect command", EffectCommandEvent.class);
        final Effect e = Effect.parse(command, null);
        ScriptLoader.deleteCurrentEvent();
       
        if (e != null) {
          log.clear(); // ignore warnings and stuff
          log.printLog();
         
          sender.sendMessage(ChatColor.GRAY + "executing '" + ChatColor.stripColor(command) + "'");
          if (SkriptConfig.logPlayerCommands.value() && !(sender instanceof ConsoleCommandSender))
            Skript.info(sender.getName() + " issued effect command: " + command);
          e.run(new EffectCommandEvent(sender, command));
        } else {
          if (sender == Bukkit.getConsoleSender()) // log as SEVERE instead of INFO like printErrors below
            SkriptLogger.LOGGER.severe("Error in: " + ChatColor.stripColor(command));
          else
            sender.sendMessage(ChatColor.RED + "Error in: " + ChatColor.GRAY + ChatColor.stripColor(command));
          log.printErrors(sender, "(No specific information is available)");
        }
      } finally {
        log.stop();
      }
      return true;
    } catch (final Exception e) {
      Skript.exception(e, "Unexpected error while executing effect command '" + command + "' by '" + sender.getName() + "'");
      sender.sendMessage(ChatColor.RED + "An internal error occurred while executing this effect. Please refer to the server log for details.");
      return true;
    } finally {
      Language.setUseLocal(wasLocal);
    }
  }
 
  @SuppressWarnings("null")
  private final static Pattern commandPattern = Pattern.compile("(?i)^command /?(\\S+)(\\s+(.+))?$"),
      argumentPattern = Pattern.compile("<\\s*(?:(.+?)\\s*:\\s*)?(.+?)\\s*(?:=\\s*(" + SkriptParser.wildcard + "))?\\s*>");
 
  @Nullable
  public final static ScriptCommand loadCommand(final SectionNode node) {
    final String key = node.getKey();
    if (key == null)
      return null;
    final String s = ScriptLoader.replaceOptions(key);
   
    int level = 0;
    for (int i = 0; i < s.length(); i++) {
      if (s.charAt(i) == '[') {
        level++;
      } else if (s.charAt(i) == ']') {
        if (level == 0) {
          Skript.error("Invalid placement of [optional brackets]");
          return null;
        }
        level--;
      }
    }
    if (level > 0) {
      Skript.error("Invalid amount of [optional brackets]");
      return null;
    }
   
    Matcher m = commandPattern.matcher(s);
    final boolean a = m.matches();
    assert a;
   
    final String command = "" + m.group(1).toLowerCase();
    final ScriptCommand existingCommand = commands.get(command);
    if (existingCommand != null && existingCommand.getLabel().equals(command)) {
      final File f = existingCommand.getScript();
      Skript.error("A command with the name /" + command + " is already defined" + (f == null ? "" : " in " + f.getName()));
      return null;
    }
   
    final String arguments = m.group(3) == null ? "" : m.group(3);
    final StringBuilder pattern = new StringBuilder();
   
    List<Argument<?>> currentArguments = new ArrayList<Argument<?>>();
    m = argumentPattern.matcher(arguments);
    int lastEnd = 0;
    int optionals = 0;
    for (int i = 0; m.find(); i++) {
      pattern.append(escape("" + arguments.substring(lastEnd, m.start())));
      optionals += StringUtils.count(arguments, '[', lastEnd, m.start());
      optionals -= StringUtils.count(arguments, ']', lastEnd, m.start());
     
      lastEnd = m.end();
     
      ClassInfo<?> c;
      c = Classes.getClassInfoFromUserInput("" + m.group(2));
      final NonNullPair<String, Boolean> p = Utils.getEnglishPlural("" + m.group(2));
      if (c == null)
        c = Classes.getClassInfoFromUserInput(p.getFirst());
      if (c == null) {
        Skript.error("Unknown type '" + m.group(2) + "'");
        return null;
      }
      final Parser<?> parser = c.getParser();
      if (parser == null || !parser.canParse(ParseContext.COMMAND)) {
        Skript.error("Can't use " + c + " as argument of a command");
        return null;
      }
     
      final Argument<?> arg = Argument.newInstance(m.group(1), c, m.group(3), i, !p.getSecond(), optionals > 0);
      if (arg == null)
        return null;
      currentArguments.add(arg);
     
      if (arg.isOptional() && optionals == 0) {
        pattern.append('[');
        optionals++;
      }
      pattern.append("%" + (arg.isOptional() ? "-" : "") + Utils.toEnglishPlural(c.getCodeName(), p.getSecond()) + "%");
    }
   
    pattern.append(escape("" + arguments.substring(lastEnd)));
    optionals += StringUtils.count(arguments, '[', lastEnd);
    optionals -= StringUtils.count(arguments, ']', lastEnd);
    for (int i = 0; i < optionals; i++)
      pattern.append(']');
   
    String desc = "/" + command + " ";
    final boolean wasLocal = Language.setUseLocal(true); // use localised class names in description
    try {
      desc += StringUtils.replaceAll(pattern, "(?<!\\\\)%-?(.+?)%", new Callback<String, Matcher>() {
        @Override
        public String run(final @Nullable Matcher m) {
          assert m != null;
          final NonNullPair<String, Boolean> p = Utils.getEnglishPlural("" + m.group(1));
          final String s = p.getFirst();
          return "<" + Classes.getClassInfo(s).getName().toString(p.getSecond()) + ">";
        }
      });
    } finally {
      Language.setUseLocal(wasLocal);
    }
    desc = unescape(desc);
    desc = "" + desc.trim();
   
    node.convertToEntries(0);
    commandStructure.validate(node);
    if (!(node.get("trigger") instanceof SectionNode))
      return null;
   
    final String usage = ScriptLoader.replaceOptions(node.get("usage", desc));
    final String description = ScriptLoader.replaceOptions(node.get("description", ""));
    ArrayList<String> aliases = new ArrayList<String>(Arrays.asList(ScriptLoader.replaceOptions(node.get("aliases", "")).split("\\s*,\\s*/?")));
    if (aliases.get(0).startsWith("/"))
      aliases.set(0, aliases.get(0).substring(1));
    else if (aliases.get(0).isEmpty())
      aliases = new ArrayList<String>(0);
    final String permission = ScriptLoader.replaceOptions(node.get("permission", ""));
    final String permissionMessage = ScriptLoader.replaceOptions(node.get("permission message", ""));
    final SectionNode trigger = (SectionNode) node.get("trigger");
    if (trigger == null)
      return null;
    final String[] by = ScriptLoader.replaceOptions(node.get("executable by", "console,players")).split("\\s*,\\s*|\\s+(and|or)\\s+");
    int executableBy = 0;
    for (final String b : by) {
      if (b.equalsIgnoreCase("console") || b.equalsIgnoreCase("the console")) {
        executableBy |= ScriptCommand.CONSOLE;
      } else if (b.equalsIgnoreCase("players") || b.equalsIgnoreCase("player")) {
        executableBy |= ScriptCommand.PLAYERS;
      } else {
        Skript.warning("'executable by' should be either be 'players', 'console', or both, but found '" + b + "'");
      }
    }
   
    if (!permissionMessage.isEmpty() && permission.isEmpty()) {
      Skript.warning("command /" + command + " has a permission message set, but not a permission");
    }
   
    if (Skript.debug() || node.debug())
      Skript.debug("command " + desc + ":");
   
    final File config = node.getConfig().getFile();
    if (config == null) {
      assert false;
      return null;
    }
   
    Commands.currentArguments = currentArguments;
    final ScriptCommand c;
    try {
      c = new ScriptCommand(config, command, "" + pattern.toString(), currentArguments, description, usage, aliases, permission, permissionMessage, executableBy, ScriptLoader.loadItems(trigger));
    } finally {
      Commands.currentArguments = null;
    }
   
    registerCommand(c);
   
    if (Skript.logVeryHigh() && !Skript.debug())
      Skript.info("registered command " + desc);
    currentArguments = null;
    return c;
  }
 
//  public static boolean skriptCommandExists(final String command) {
//    final ScriptCommand c = commands.get(command);
//    return c != null && c.getName().equals(command);
//  }
 
  public static void registerCommand(final ScriptCommand command) {
    if (commandMap != null) {
      assert cmKnownCommands != null;// && cmAliases != null;
      command.register(commandMap, cmKnownCommands, cmAliases);
    }
    commands.put(command.getLabel(), command);
    for (final String alias : command.getActiveAliases()) {
      commands.put(alias.toLowerCase(), command);
    }
    command.registerHelp();
  }
 
  public static int unregisterCommands(final File script) {
    int numCommands = 0;
    final Iterator<ScriptCommand> commandsIter = commands.values().iterator();
    while (commandsIter.hasNext()) {
      final ScriptCommand c = commandsIter.next();
      if (script.equals(c.getScript())) {
        numCommands++;
        c.unregisterHelp();
        if (commandMap != null) {
          assert cmKnownCommands != null;// && cmAliases != null;
          c.unregister(commandMap, cmKnownCommands, cmAliases);
        }
        commandsIter.remove();
      }
    }
    return numCommands;
  }
 
  private static boolean registeredListeners = false;
 
  public final static void registerListeners() {
    if (!registeredListeners) {
      Bukkit.getPluginManager().registerEvents(commandListener, Skript.getInstance());
      Bukkit.getPluginManager().registerEvents(post1_3chatListener != null ? post1_3chatListener : pre1_3chatListener, Skript.getInstance());
      registeredListeners = true;
    }
  }
 
  public final static void clearCommands() {
    final SimpleCommandMap commandMap = Commands.commandMap;
    if (commandMap != null) {
      final Map<String, Command> cmKnownCommands = Commands.cmKnownCommands;
      final Set<String> cmAliases = Commands.cmAliases;
      assert cmKnownCommands != null;// && cmAliases != null;
      for (final ScriptCommand c : commands.values())
        c.unregister(commandMap, cmKnownCommands, cmAliases);
    }
    for (final ScriptCommand c : commands.values()) {
      c.unregisterHelp();
    }
    commands.clear();
  }
 
  /**
   * copied from CraftBukkit (org.bukkit.craftbukkit.help.CommandAliasHelpTopic)
   */
  public final static class CommandAliasHelpTopic extends HelpTopic {
   
    private final String aliasFor;
    private final HelpMap helpMap;
   
    public CommandAliasHelpTopic(final String alias, final String aliasFor, final HelpMap helpMap) {
      this.aliasFor = aliasFor.startsWith("/") ? aliasFor : "/" + aliasFor;
      this.helpMap = helpMap;
      name = alias.startsWith("/") ? alias : "/" + alias;
      Validate.isTrue(!name.equals(this.aliasFor), "Command " + name + " cannot be alias for itself");
      shortText = ChatColor.YELLOW + "Alias for " + ChatColor.WHITE + this.aliasFor;
    }
   
    @Override
    public String getFullText(final @Nullable CommandSender forWho) {
      final StringBuilder sb = new StringBuilder(shortText);
      final HelpTopic aliasForTopic = helpMap.getHelpTopic(aliasFor);
      if (aliasForTopic != null) {
        sb.append("\n");
        sb.append(aliasForTopic.getFullText(forWho));
      }
      return "" + sb.toString();
    }
   
    @Override
    public boolean canSee(final @Nullable CommandSender commandSender) {
      if (amendedPermission == null) {
        final HelpTopic aliasForTopic = helpMap.getHelpTopic(aliasFor);
        if (aliasForTopic != null) {
          return aliasForTopic.canSee(commandSender);
        } else {
          return false;
        }
      } else {
        return commandSender != null && commandSender.hasPermission(amendedPermission);
      }
    }
  }
 
}
TOP

Related Classes of ch.njol.skript.command.Commands

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.