All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ru.cwcode.commands.Command Maven / Gradle / Ivy

package ru.cwcode.commands;

import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextColor;
import org.jetbrains.annotations.NotNull;
import ru.cwcode.commands.api.CommandsAPI;
import ru.cwcode.commands.api.Sender;
import ru.cwcode.commands.color.ColorGenerationStrategy;
import ru.cwcode.commands.color.ColoredScheme;
import ru.cwcode.commands.color.DefaultColorGenerationStrategy;
import ru.cwcode.commands.executor.AbstractExecutor;
import ru.cwcode.commands.permissions.DefaultPermissionGenerationStrategy;
import ru.cwcode.commands.permissions.PermissionGenerationStrategy;
import ru.cwcode.commands.permissions.ProcessResult;
import ru.cwcode.commands.preconditions.CommandPreconditionResult;
import ru.cwcode.commands.preconditions.Precondition;
import ru.cwcode.commands.preconditions.PredicatePrecondition;
import ru.cwcode.commands.preconditions.processor.PermissionPrecondition;
import ru.cwcode.commands.preconditions.processor.PreconditionProcessor;
import ru.cwcode.commands.preconditions.processor.PreconditionRequirements;
import ru.cwcode.commands.preconditions.processor.PreconditionResult;

import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static ru.cwcode.commands.api.CommandsAPI.l10n;

public class Command implements Permissible {
  protected List argumentSets = new ArrayList<>();
  String name;
  ColorGenerationStrategy color = null;
  PermissionGenerationStrategy permissions = null;
  List aliases = new ArrayList<>();
  boolean isSubcommand = false;
  String description = null;
  String permission;
  Deque preconditions = new ArrayDeque<>();
  Help help;
  Command parent = null;
  Command[] subcommands = new Command[]{};
  //присваивается только в рут-команде
  DebugMode debug = DebugMode.NONE;
  
  /**
   * Автоматически устанавливается пермишен name и устанавливаются алиасы
   */
  public Command(String name, List aliases) {
    this(name);
    aliases(aliases);
  }
  
  /**
   * Автоматически устанавливается пермишен name
   */
  public Command(String name) {
    this.name = name;
    this.permission = name;
  }
  
  /**
   * Устанавливает алиасы для команды. Не работает для рут-команды. Переписывает текущие алиасы
   */
  public Command aliases(List aliases) {
    this.aliases = aliases;
    return this;
  }
  
  /**
   * Шоткат для сингл-аргументсета в команде без аргументов
   */
  public Command(String name, AbstractExecutor executor) {
    this(name);
    arguments(new ArgumentSet(executor, name));
  }
  
  /**
   * Добавляет аргументсеты в команду или подкоманду
   */
  public Command arguments(ArgumentSet... arguments) {
    for (ArgumentSet set : arguments) {
      argumentSets.add(set);
      
      if (debug.is(DebugMode.DETAILED))
        debug.print(l10n.get("command.debug.registeredArgumentSet", name, set));
      
      if (set.optionalStart >= 0) {
        for (int i = set.arguments.length - 1; i >= set.optionalStart; i--) { //делает все возможные варианты без опциональных аргументов
          argumentSets.add(new ArgumentSet(set, Arrays.copyOfRange(set.arguments, 0, i)).hidden());
          
          if (debug.is(DebugMode.DETAILED)) debug.print(l10n.get("command.debug.optionalAdapt", set));
        }
      }
    }
    
    return this;
  }
  
  /**
   * Шоткат для сингл-аргументсета в команде без аргументов с кастомным пермишеном
   */
  public Command(String name, String permission, AbstractExecutor executor) {
    this(name, permission);
    arguments(new ArgumentSet(executor, name));
  }
  
  /**
   * Указывайте пермишен "" для того, чтобы пермишены рекурсивно не генерировались
   */
  public Command(String name, String permission) {
    this(name);
    this.permission = permission;
  }
  
  /**
   * Шоткат для сингл-аргументсета в команде с любыми аргументами
   */
  public Command(String name, AbstractExecutor executor, Argument... arguments) {
    this(name);
    arguments(new ArgumentSet(executor, "", arguments));
  }
  
  @Deprecated(forRemoval = true)
  public Command setIgnoreExecutionPossibility(boolean ignoreExecutionPossibility) {
    return this;
  }
  
  /**
   * Устанавливает алиасы для команды. Не работает для рут-команды. Переписывает текущие алиасы
   */
  public Command aliases(String... aliases) {
    this.aliases = List.of(aliases);
    return this;
  }
  
  /**
   * Возвращает алиасы команды
   */
  public List aliases() {
    return this.aliases;
  }
  
  /**
   * Добавляет подкоманды в команду. Можно использовать несколько раз
   */
  public Command subCommands(Command... subcommands) {
    this.subcommands = subcommands;
    for (Command subcommand : subcommands) {
      subcommand.isSubcommand = true;
      subcommand.parent = this;
      subcommand.debug = debug;
      
      if (debug.is(DebugMode.DETAILED))
        debug.print(l10n.get("command.debug.registeredSubcommand", getName(), subcommand.getName()));
    }
    
    return this;
  }
  
  /**
   * Регистрирует команду. Вызывать только раз.
   */
  public void register() {
    
    if (isSubcommand) return;
    if (debug != DebugMode.NONE) updateDebug(debug);
    if (permissions == null) permissions = new DefaultPermissionGenerationStrategy();
    if (color == null) color = DefaultColorGenerationStrategy.INSTANCE;
    
    this.name = this.name.toLowerCase();
    
    updatePermissions(permission);
    
    CommandsAPI.getPlatform().registerCommand(this);
  }
  
  /**
   * Очищает аргументсеты из команды или подкоманды
   */
  public void clearArgumentSets() {
    this.argumentSets.clear();
  }
  
  /**
   * Устанавливает свой хелп вместо авто-генерируемого
   */
  public Command help(Help help) {
    this.help = help;
    return this;
  }
  
  /**
   * Устанавливает краткое описание команды для автогенерируемого хелпа
   */
  public Command description(String shortDescription) {
    this.description = shortDescription;
    return this;
  }
  
  public String description() {
    return description;
  }
  
  public Command debug(DebugMode mode) {
    this.debug = mode;
    return this;
  }
  
  public Command getRootCommand() {
    if (isSubcommand) return parent.getRootCommand();
    return this;
  }
  
  public ColorGenerationStrategy getColorScheme() {
    if (color == null) return parent.getColorScheme();
    return color;
  }
  
  /**
   * Устанавливает цветовое оформление для хелпа и других сообщений.
   */
  public Command setColorScheme(ColorGenerationStrategy colorGenerationStrategy) {
    this.color = colorGenerationStrategy;
    return this;
  }
  
  public Command setColorScheme(TextColor color) {
    setColorScheme(new ColoredScheme(color));
    return this;
  }
  
  public String getName() {
    return name;
  }
  
  /**
   * Устанавливает новое имя для команды, если это подкоманда
   */
  public Command setName(String name) {
    if (this.isSubcommand) {
      this.name = name;
    }
    return this;
  }
  
  @Override
  public String getPermission() {
    return permission;
  }
  
  PermissionGenerationStrategy getPermissions() {
    if (permissions == null) {
      if (parent == null) return null;
      return parent.getPermissions();
    }
    return permissions;
  }
  
  /**
   * @param strategy стратегия генерации пермишенов
   *                 Использовать только в рут-команде
   */
  public Command setPermissions(PermissionGenerationStrategy strategy) {
    this.permissions = strategy;
    return this;
  }
  
  /**
   * Предикат, который проверяется при автокомплите, выводе хелпа и попытке выполнения
   */
  public Command canExecute(Predicate canExecute) {
    this.preconditions.add(new PredicatePrecondition(canExecute));
    return this;
  }
  
  
  /**
   * Добавляет Precondition`s в конец списка
   */
  public Command preconditions(Precondition... preconditions) {
    this.preconditions.addAll(Arrays.asList(preconditions));
    return this;
  }
  
  protected void updatePermissions(String permissions) {
    ProcessResult result;
    
    if (isSubcommand) {
      result = getPermissions().processSubCommand(permissions, permission, name);
      
      if (debug.is(DebugMode.DETAILED))
        debug.print(l10n.get("command.debug.subcommandPermissionSet", name, result.getPermission()));
    } else {
      result = getPermissions().processCommand(permission, name);
      
      if (debug.is(DebugMode.DETAILED))
        debug.print(l10n.get("command.debug.commandPermissionSet", name, result.getPermission()));
    }
    
    permissions = result.getNextPermissions();
    permission = result.getPermission();
    updatePermissionPrecondition();
    
    for (Command subcommand : subcommands) {
      subcommand.updatePermissions(permissions);
    }
    
    for (ArgumentSet argumentSet : argumentSets) {
      argumentSet.permission = getPermissions().processArgumentSet(permissions, argumentSet.permission, permission);
      argumentSet.updatePermissionPrecondition();
      
      if (debug.is(DebugMode.DETAILED))
        debug.print(l10n.get("command.debug.argumentSetPermissionSet", name + argumentSet, argumentSet.permission));
    }
  }
  
  private void updatePermissionPrecondition() {
    for (Precondition precondition : preconditions) {
      if (precondition instanceof PermissionPrecondition) {
        return;
      }
    }
    
    preconditions.addFirst(new PermissionPrecondition(this));
  }
  
  protected void onExecute(Sender sender, String[] args, ArgumentSet founded) {
    var start = System.nanoTime();
    
    founded.execute(sender, args, this);
    
    if (debug.is(DebugMode.REDUCED))
      debug.print(l10n.get("command.debug.executionTook", founded, (System.nanoTime() - start), (System.nanoTime() - start) / 1000000));
  }
  
  protected List getSubcommandsFor(Sender sender, PreconditionRequirements requirements) {
    List list = new ArrayList<>();
    
    for (Command command : subcommands) {
      if (command.checkPreconditions(sender, requirements)) {
        list.add(command);
      }
    }
    
    return list;
  }
  
  protected boolean canPerformedBy(Sender sender) {
    return checkPreconditions(sender).getResult().canPerform();
  }
  
  protected CommandPreconditionResult checkPreconditions(Sender sender) {
    return new CommandPreconditionResult(this, PreconditionProcessor.process(sender, preconditions));
  }
  
  protected boolean checkPreconditions(Sender sender, PreconditionRequirements requirements) {
    return PreconditionProcessor.process(sender, preconditions).isSatisfy(requirements);
  }
  
  protected Command getSubcommandFor(String arg, Sender sender) {
    
    for (Command command : subcommands) {
      if ((command.name.equalsIgnoreCase(arg) || command.aliases.contains(arg))
          && command.checkPreconditions(sender, PreconditionRequirements.CAN_PERFORM_AND_CAN_SEE)) {
        return command;
      }
    }
    
    return null;
  }
  
  
  protected List getArgumentSetsFor(Sender sender, PreconditionRequirements requirements) {
    List list = new ArrayList<>();
    
    for (ArgumentSet arg : argumentSets) {
      if (arg.checkPreconditions(sender, requirements)) {
        list.add(arg);
      }
    }
    return list;
  }
  
  protected ArgumentSearchResult searchForArgumentSet(Sender sender, String... args) {
    ArgumentSearchResult result = new ArgumentSearchResult();
    
    for (ArgumentSet set : argumentSets) {
      PreconditionResult preconditionResult = set.checkPreconditions(sender);
      
      ArgumentFitnessResult fitnessResult = set.isArgumentsFit(sender, args);
      
      String errorMessage = null;
      if (!preconditionResult.canPerform()) {
        if (preconditionResult.getCannotPerformMessage() != null) {
          errorMessage = preconditionResult.getCannotPerformMessage();
        } else continue;
      }
      
      if (fitnessResult.success()) {
        if (errorMessage == null) {
          return result.founded(set);
        } else {
          return result.error(set, errorMessage);
        }
      } else {
        if (preconditionResult.canSee()) result.add(fitnessResult);
      }
    }
    
    return result;
  }
  
  protected void onError(Sender sender, String label, String[] args, ArgumentSearchResult argumentSearchResult) {
    if (argumentSearchResult.getErrorMessage() != null) {
      showErrorMessage(sender, label, argumentSearchResult);
    } else if (argumentSearchResult.canShowDetailedHelp()) {
      showDetailedHelp(sender, label, argumentSearchResult);
    } else {
      showFullHelp(sender, label, args);
    }
  }
  
  private void showErrorMessage(Sender sender, String label, ArgumentSearchResult argumentSearchResult) {
    List toSend = new ArrayList<>();
    
    toSend.add(Component.empty());
    
    toSend.add(argumentSearchResult.getFounded().toComponent(sender, getColorScheme()));
    toSend.add(Component.text("↳ ")
                        .append(Component.text(argumentSearchResult.getErrorMessage()))
                        .color(getColorScheme().accent(true)));
    
    toSend.add(Component.empty());
    
    for (Component line : toSend) {
      sender.sendMessage(line);
    }
  }
  
  protected void showFullHelp(Sender sender, String label, String[] args) {
    if (help == null) {
      sendAutoHelp(sender, label, args);
    } else {
      help.sendTo(sender, this, label, args);
    }
  }
  
  private void updateDebug(DebugMode debug) {
    this.debug = debug;
    
    for (Command subcommand : subcommands) {
      subcommand.updateDebug(debug);
    }
  }
  
  private void showDetailedHelp(Sender sender, String label, ArgumentSearchResult argumentSearchResult) {
    Component written = Component.text("  /" + getFullCommandPath(label), getColorScheme().written(true));
    List toSend = new ArrayList<>();
    
    toSend.add(Component.empty());
    
    for (ArgumentFitnessResult invalidResult : argumentSearchResult.getInvalidResults()) {
      toSend.add(written.append(invalidResult.getArgumentSet().toComponent(sender, getColorScheme())));
      toSend.add(Component.text("↳ ")
                          .append(invalidResult.getInvalidArgument().invalidMessage(this, sender, invalidResult.getInvalidStringArgument()))
                          .color(getColorScheme().accent(true)));
      toSend.add(Component.empty());
    }
    
    for (Component row : toSend) {
      sender.sendMessage(row);
    }
  }
  
  private void sendAutoHelp(Sender sender, String label, String[] args) {
    ColorGenerationStrategy color = getColorScheme();
    
    Component written = Component.text("  /" + getFullCommandPath(label));
    
    List toSend = new ArrayList<>();
    
    for (Command subcommand : getSubcommandsFor(sender, PreconditionRequirements.ONLY_CAN_SEE)) {
      boolean canPerformedBy = subcommand.canPerformedBy(sender);
      
      toSend.add(written.color(color.written(canPerformedBy))
                        .append(Component.text(" " + subcommand.name + " ", color.subcommand(canPerformedBy)))
                        .append(sender.isOp() ? Component.text(subcommand.permission, color.permissions(canPerformedBy)) : Component.empty())
      );
    }
    
    boolean previousWasEmptyLine = false;
    
    for (ArgumentSet argumentSet : filterArgumentSets(getArgumentSetsFor(sender, PreconditionRequirements.ONLY_CAN_SEE), args)) {
      
      if (argumentSet.isHidden()) continue;
      
      boolean canPerformedBy = argumentSet.canPerformedBy(sender);
      boolean hasHelp = argumentSet.hasHelp();
      
      if (!previousWasEmptyLine && hasHelp) toSend.add(Component.empty());
      
      toSend.add(written.color(color.written(canPerformedBy))
                        .append(argumentSet.toComponent(sender, color)));
      
      if (hasHelp) {
        toSend.add(Component.text("↳ ")
                            .append(argumentSet.help)
                            .color(color.accent(canPerformedBy)));
        
        toSend.add(Component.empty());
        previousWasEmptyLine = true;
      } else {
        previousWasEmptyLine = false;
      }
    }
    
    sendDescription(sender, color);
    
    sender.sendMessage("");
    
    if (toSend.isEmpty()) {
      sender.sendMessage(Component.text(l10n.get("command.noContinuation"), color.main()));
    } else {
      sender.sendMessage(Component.text(l10n.get("command.continuations"), color.main()));
      sender.sendMessage("");
      
      for (Component row : toSend) {
        sender.sendMessage(row);
      }
    }
  }
  
  private List filterArgumentSets(List argumentSets, String[] args) {
    if (args.length == 0) return argumentSets;
    
    List relevantArgumentSets = argumentSets.stream().filter(x -> x.shouldShowInHelp(List.of(args))).collect(Collectors.toList());
    
    if (relevantArgumentSets.isEmpty()) return argumentSets;
    
    return relevantArgumentSets;
  }
  
  private void sendDescription(Sender sender, ColorGenerationStrategy color) {
    if (description != null) {
      sender.sendMessage("");
      sender.sendMessage(Component.text(l10n.get("command.description"), color.main()));
      
      boolean firstLine = true;
      
      for (String part : description.split("\n")) {
        sender.sendMessage(Component.text((firstLine ? "↳ " : "  ") + part, color.accent(true)));
        firstLine = false;
      }
    }
  }
  
  @NotNull
  public String getFullCommandPath(String label) {
    StringBuilder writtenString = new StringBuilder();
    
    Command rootCommand = this;
    
    while (rootCommand.isSubcommand) {
      writtenString.insert(0, " " + rootCommand.name);
      rootCommand = rootCommand.parent;
    }
    
    writtenString.insert(0, label);
    return writtenString.toString();
  }
  
  private int getCommandPathLength() {
    int length = 0;
    
    Command parent = this;
    
    while (parent != null) {
      length++;
      parent = parent.parent;
    }
    
    return length;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy