Команды

Команды

Версия(и) Minecraft
1.7.10, 1.12.2
Всем доброго времени суток. В этом туториале я расскажу о том, как добавлять собственные команды в игру. Исходники доступны на GitHub: 1.7.10 и 1.12.2. Они содержат по три примера команд, а в этой статье я расскажу как создавать одну из них.

Команды
Все доступные в игре команды предназначены для сервера, однако они работают и в одиночной игре. В чём же секрет? Всё дело в том, что даже в одиночной игре присутствует сервер, но только логический, поэтому нам доступны все серверные объекты. Команды создаются для сервера и регистрируются в процессе его загрузки и его наличие в одиночной игре даёт нам возможность их использовать, конечно только если при создании мира вы разрешили использование команд (cheats).

Так как выполнение команд происходит на сервере, то вся активность не должна использовать объекты клиента. Если требуется что то синхронизировать с клиентом - придётся использовать пакеты. Кроме того в любом случае вам потребуется настроить прокси.

1.7.10
Подготовка

Для начала в главном классе вашего проекта создайте метод, который будет выполнятся при загрузке сервера:
Java:
    @EventHandler
    public void serverStarting(FMLServerStartingEvent event) {}


В CommonProxy создаёте такой же, но без аннотации. Вызовите его из метода в главном классе:
Java:
    public void serverStarting(FMLServerStartingEvent event) {}//В CommonProxy

    @EventHandler
    public void serverStarting(FMLServerStartingEvent event) {
  
        this.proxy.serverStarting(event);//Вызов из CommonProxy в главном классе
    }


В нём то и будем регистрировать команды через вызов FMLServerStartingEvent#registerServerCommand()

Создание команды

Начнём. Создадим команду для ремонта предмета в руке, класс CommandRepair:
Java:
public class CommandRepair extends CommandBase {

    @Override
    public String getCommandName() {
      
        return null;
    }

    @Override
    public String getCommandUsage(ICommandSender commandSender) {
      
        return null;
    }

    @Override
    public void processCommand(ICommandSender commandSender, String[] args) {}
}


Есть два пути: унаследовать CommandBase или реализовать ICommand. Я выбрал первый вариант, так как CommandBase содержит базовую реализацию интерфейса и добавляет множество полезных методов для парсинга команд (аргументов команд). Настоятельно рекомендую ознакомится с их содержимым, а также с классами в пакете net.minecraft.command для получения исчерпывающего представления об использовании на примерах ванильных команд.

Рассмотрим методы, которые обязательно требуется переопределить.

getCommandName() отвечает за имя, которое используется при вызове. Кроме имени мы ещё можем передавать множество аргументов при вызове. Этот пример не будет использовать аргументы.

getCommandUsage() возвращает шаблон использования. Он выводится при выбрасывании исключения WrongUsageException, а так же при просмотре доступных команд при вызове /help.

Теперь перейдём к processCommand() - этот метод выполняется при вызове команды. Как я уже писал, выполняется он только на сервере. В качестве аргументов у нас есть сам сервер, объект, вызвавший команду (игрок или консоль) и аргументы, с которыми она вызвана. Тут необходимо определить исполняемую активность.

Реализация. Для getCommandName() и getCommandUsage() созданы две строковые константы в начале класса для удобства. В getCommandUsage() добавлена необходимая активность:
Java:
public class CommandRepair extends CommandBase {
  
    /*
     * При вызове команды предмет в руке полностью ремонтируется. Доступна опнутым игрокам.
     *
     * Использование:
     *
     * /repair,
     * /r
     */

    public static final String
    NAME = "repair",//Имя команды, используется при вызове.
    USAGE = "/repair";//Шаблон вызова, выводится при выбрасывании WrongUsageException.
  
    @Override
    public String getCommandName() {
      
        return this.NAME;
    }

    @Override
    public String getCommandUsage(ICommandSender commandSender) {
      
        return this.USAGE;
    }
  
    @Override
    public void processCommand(ICommandSender commandSender, String[] args) {
      
        if (commandSender instanceof EntityPlayer) {
          
            if (args.length > 0) {
                                      
                throw new WrongUsageException(this.getCommandUsage(commandSender));//Выбросить исключение если вместе с командой переданы какие либо аргументы.
            }
          
            EntityPlayer player = this.getCommandSenderAsPlayer(commandSender);//Получение экземрляра игрока, вызвавшего команду.      
                      
            ItemStack mainHandItem = player.getHeldItem();
                                                          
            if (mainHandItem != null) {
                                                                                                                      
                  
                if (mainHandItem.isItemStackDamageable()) {
              
                    if (mainHandItem.isItemDamaged()) {
                          
                        mainHandItem.setItemDamage(0);//Если предмет может быть повреждён и имеет повреждение - ремонт.                                                  
                    }
                }
            }          
        }
    }
}


Впринципе всё, команда будет работать. Осталось зарегистрировать её в CommonProxy в методе serverStarting():
Java:
    public void serverStarting(FMLServerStartingEvent event) {
  
        event.registerServerCommand(new CommandRepair());
    }


Вы можете зайти и проверить её работоспособность. По умолчанию она будет доступна только для миров, в которых разрешены команды (читы) или для опнутых игроков на сервере.

Дополнительно: права

В первую очередь хотелось бы обратить внимание на права использования. По умолчанию команда может быть использована опами на сервере или в одиночной игре при разрешённых командах (читах). Конечно вы можете добавить проверки в метод processCommand(), однако для этого уже есть специализированный метод canCommandSenderUseCommand():
Java:
    @Override
    public boolean canCommandSenderUseCommand(ICommandSender commandSender) {
      
        //Только опам или если в мире активны читы.
        return commandSender instanceof EntityPlayer ? MinecraftServer.getServer().getConfigurationManager().func_152596_g(((EntityPlayer) commandSender).getGameProfile()) : false;      
    }


Он вызывается до processCommand(). К примеру просто вернув в нём true вы разрешите использование команды кем угодно и где угодно.

Дополнительно: варианты вызова

Для большего удобства каждая команда может быть дополнена набором вариантов вызова. Распространённое применение этой возможности заключается в добавлении укороченных вариантов. Добиться этого можно переопределив getCommandAliases():
Java:
    @Override
    public List<String> getCommandAliases() {
  
        List<String> aliases = new ArrayList<String>();//Так как допустимых вариаций может быть много, передаются они массивом строк

        //добавление вариаций
  
        return aliases;
    }


Дополнительно: уведомления

Большинство команд при вызове выводят в чат некоторые сообщения с результатами. Сообщения могут выводится для игрока, вызвавшего команду, всех игроков на сервере или кого то конкретного.

Для вывода сообщения игроку, вызвавшему команду:
CommandBase#getCommandSenderAsPlayer().addChatMessage()

Для всех игроков на сервере:
MinecraftServer#getServer()#getConfigurationManager()#sendChatMsg()

Для игрока по нику, переданному как аргумент при вызове команды:
CommandBase#getPlayer().sendMessage()

Для вывода таких сообщений строго рекомендуется использовать ChatComponentTranslation, такое сообщение будет автоматически локализовано при выводе в чат в зависимости от выбранного языка клиента.

Дополнительно: команда исключительно для клиента

Что бы команда работала только на клиенте, вам надо зарегистрировать её через ClientCommandHandler.instance.registerCommand() в ClientProxy в процессе преинициализации (например). Следует учесть, что такая команда не имеет возможности влиять на данные сервера (физического или логического). Метод processCommand() в данном случае может ссылаться на клиентские классы.

Заключение

Вот как выглядит класс команды после внесения дополнений:
Java:
public class CommandRepair extends CommandBase {
  
    /*
     * При вызове команды предмет в руке полностью ремонтируется. Доступна опнутым игрокам.
     *
     * Использование:
     *
     * /repair,
     * /r
     */

    public static final String
    NAME = "repair",//Имя команды, используется при вызове.
    ALIAS = "r",//Допустимая вариация команды, таких может быть несколько.
    USAGE = "/repair";//Шаблон вызова, выводится при выбрасывании WrongUsageException.
  
    @Override
    public String getCommandName() {
      
        return this.NAME;
    }

    @Override
    public String getCommandUsage(ICommandSender commandSender) {
      
        return this.USAGE;
    }
  
    @Override
    public List<String> getCommandAliases() {
      
        List<String> aliases = new ArrayList<String>();//Так как допустимых вариаций может быть много, передаются они массивом строк.
      
        aliases.add(this.ALIAS);
      
        return aliases;
    }
  
    @Override
    public boolean canCommandSenderUseCommand(ICommandSender commandSender) {
      
        //Только опам или если в мире активны читы.
        return commandSender instanceof EntityPlayer ? MinecraftServer.getServer().getConfigurationManager().func_152596_g(((EntityPlayer) commandSender).getGameProfile()) : false;      
    }
  
    @Override
    public void processCommand(ICommandSender commandSender, String[] args) {
      
        if (commandSender instanceof EntityPlayer) {
          
            if (args.length > 0) {
                                      
                throw new WrongUsageException(this.getCommandUsage(commandSender));//Выбросить исключение если вместе с командой переданы какие либо аргументы.
            }
          
            EntityPlayer player = this.getCommandSenderAsPlayer(commandSender);//Получение экземрляра игрока, вызвавшего команду.      
                      
            ItemStack mainHandItem = player.getHeldItem();
          
            ChatComponentTranslation chatMessage;//Ссылка на сообщение, которое будет выведено в чат игрока.
                                              
            if (mainHandItem != null) {
                  
                chatMessage = new ChatComponentTranslation("commands.repair.item");//Начало сообщения - "Предмет".
                  
                chatMessage.getChatStyle().setColor(EnumChatFormatting.RED);//Установка цвета сообщения.
                                      
                ChatComponentTranslation itemName = new ChatComponentTranslation(" <" + mainHandItem.getDisplayName() + "> ");//Отдельный объект TextComponentTranslation для имени предмета.
                  
                itemName.getChatStyle().setColor(EnumChatFormatting.WHITE);
                  
                chatMessage.appendSibling(itemName);//Сцепление - "Предмет <Имя> ".
                  
                if (mainHandItem.isItemStackDamageable()) {
              
                    if (mainHandItem.isItemDamaged()) {
                          
                        mainHandItem.setItemDamage(0);//Если предмет может быть повреждён и имеет повреждение - ремонт.
                      
                        chatMessage.getChatStyle().setColor(EnumChatFormatting.GREEN);
                          
                        chatMessage.appendSibling(new ChatComponentTranslation("commands.repair.repaired"));//"Предмет <Имя> отремонтирован".
                    }
                      
                    else {
                          
                        //Если предмет не повреждён.
                      
                        chatMessage.appendSibling(new ChatComponentTranslation("commands.repair.noDamage"));//"Предмет <Имя> отремонтирован".
                    }
                }
                  
                else {
                  
                    //Если предмет нельзя повредить.
                      
                    chatMessage.appendSibling(new ChatComponentTranslation("commands.repair.canNotRepair"));//"Предмет <Имя> не может быть отремонтирован".
                }
            }
              
            else {
              
                //Если предмета в руке нет.
                  
                chatMessage = new ChatComponentTranslation("commands.repair.handEmpty");//"Нет активного предмета".
            }
              
            player.addChatMessage(chatMessage);//Вывод сообщения для игрока, вызвавшего команду.                  
        }
    }
}

commands_test.png

1.12.2
Подготовка

Для начала в главном классе вашего проекта создайте метод, который будет выполнятся при загрузке сервера:
Java:
    @EventHandler
    public void serverStarting(FMLServerStartingEvent event) {}


В CommonProxy создаёте такой же, но без аннотации. Вызовите его из метода в главном классе:
Java:
    public void serverStarting(FMLServerStartingEvent event) {}//В CommonProxy

    @EventHandler
    public void serverStarting(FMLServerStartingEvent event) {
  
        this.proxy.serverStarting(event);//Вызов из CommonProxy в главном классе
    }


В нём то и будем регистрировать команды через вызов FMLServerStartingEvent#registerServerCommand()

Создание команды

Начнём. Создадим команду для ремонта предмета в руке, класс CommandRepair:
Java:
public class CommandRepair extends CommandBase {

    @Override
    public String getName() {
  
        return null;
    }

    @Override
    public String getUsage(ICommandSender sender) {
  
        return null;
    }

    @Override
    public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException {}
}


Есть два пути: унаследовать CommandBase или реализовать ICommand. Я выбрал первый вариант, так как CommandBase содержит базовую реализацию интерфейса и добавляет множество полезных методов для парсинга команд (аргументов команд). Настоятельно рекомендую ознакомится с их содержимым, а также с классами в пакете net.minecraft.command для получения исчерпывающего представления об использовании на примерах ванильных команд.

Рассмотрим методы, которые обязательно требуется переопределить.

getName() отвечает за имя, которое используется при вызове. Кроме имени мы ещё можем передавать множество аргументов при вызове. Этот пример не будет использовать аргументы.

getUsage() возвращает шаблон использования. Он выводится при выбрасывании исключения WrongUsageException, а так же при просмотре доступных команд при вызове /help.

Теперь перейдём к execute() - этот метод выполняется при вызове команды. Как я уже писал, выполняется он только на сервере. В качестве аргументов у нас есть сам сервер, объект, вызвавший команду (игрок или консоль) и аргументы, с которыми она вызвана. Тут необходимо определить исполняемую активность.

Реализация. Для getName() и getUsage() созданы две строковые константы в начале класса для удобства. В execute() добавлена необходимая активность:
Java:
public class CommandRepair extends CommandBase {

    public static final String
    NAME = "repair",//Имя команды, используется при вызове
    USAGE = "/repair";//Шаблон вызова, выводится при выбрасывании WrongUsageException

    @Override
    public String getName() {
  
        return this.NAME;
    }

    @Override
    public String getUsage(ICommandSender sender) {
  
        return this.USAGE;
    }

    //Выполнение команды происходит здесь, этот метод вызывается только на сервере (физическом или логическом)
    @Override
    public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException {
              
        if (sender instanceof EntityPlayer) {
                                      
            if (args.length > 0) {
                                  
                throw new WrongUsageException(this.getUsage(sender));//Выбросить исключение если вместе с командой переданы какие либо аргументы.
            }
      
            EntityPlayer player = this.getCommandSenderAsPlayer(sender);//Получение экземрляра игрока, вызвавшего команду 
                  
            ItemStack mainHandItem = player.getHeldItemMainhand();
                                          
            if (mainHandItem != ItemStack.EMPTY || mainHandItem.getItem() != Items.AIR) {
                                                                                                                  
                if (mainHandItem.isItemStackDamageable()) {
          
                    if (mainHandItem.isItemDamaged()) {
                      
                        mainHandItem.setItemDamage(0);//Если предмет может быть повреждён и имеет повреждение - ремонт     
                    }
                }
            }             
        }
    }
}


Впринципе всё, команда будет работать. Осталось зарегистрировать её в CommonProxy в методе serverStarting():
Java:
    public void serverStarting(FMLServerStartingEvent event) {
  
        event.registerServerCommand(new CommandRepair());
    }


Вы можете зайти и проверить её работоспособность. По умолчанию она будет доступна только для миров, в которых разрешены команды (читы) или для опнутых игроков на сервере.

Дополнительно: права

В первую очередь хотелось бы обратить внимание на права использования. По умолчанию команда может быть использована опами на сервере или в одиночной игре при разрешённых командах (читах). Конечно вы можете добавить проверки в метод execute(), однако для этого уже есть специализированный метод checkPermission():
Java:
    public boolean checkPermission(MinecraftServer server, ICommandSender sender) {
  
        return sender instanceof EntityPlayer ? server.getPlayerList().canSendCommands(((EntityPlayer) sender).getGameProfile()) : false;//Если игрок опнут (opped), то он может использовать команду
    }


Он вызывается до execute(). К примеру просто вернув в нём true вы разрешите использование команды кем угодно и где угодно.

Дополнительно: варианты вызова

Для большего удобства каждая команда может быть дополнена набором вариантов вызова. Распространённое применение этой возможности заключается в добавлении укороченных вариантов. Добиться этого можно переопределив getAliases():
Java:
    @Override
    public List<String> getAliases() {
  
        List<String> aliases = new ArrayList<String>();//Так как допустимых вариаций может быть много, передаются они массивом строк

        //добавление вариаций
  
        return aliases;
    }


Дополнительно: уведомления

Большинство команд при вызове выводят в чат некоторые сообщения с результатами. Сообщения могут выводится для игрока, вызвавшего команду, всех игроков на сервере или кого то конкретного.

Для вывода сообщения игроку, вызвавшему команду:
CommandBase#getCommandSenderAsPlayer().sendMessage()

Для всех игроков на сервере:
MinecraftServer#getPlayerList().sendMessage()

Для игрока по нику, переданному как аргумент при вызове команды:
CommandBase#getPlayer().sendMessage()

Для вывода таких сообщений строго рекомендуется использовать TextComponentTranslation, такое сообщение будет автоматически локализовано при выводе в чат в зависимости от выбранного языка клиента.

Дополнительно: команда исключительно для клиента

Что бы команда работала только на клиенте, вам надо зарегистрировать её через ClientCommandHandler.instance.registerCommand() в ClientProxy в процессе преинициализации (например). Следует учесть, что такая команда не имеет возможности влиять на данные сервера (физического или логического). Метод execute() в данном случае может ссылаться на клиентские классы.

Клиентскую команду можно выполнять просто вводя в чат её имя, не предваряя её "/". Если уж очень хочется этого избежать, то реализуйте IClientCommand в классе вашей команды, переопределите allowUsageWithoutPrefix() и верните в нём false (без реализации этого интерфейса и переопределения по умолчанию будет возвращаться true). Таким образом для использования команды вам придётся вводить её вместе со слешем.

Заключение

Вот как выглядит класс команды после внесения дополнений:
Java:
public class CommandRepair extends CommandBase {

    /*
     * При вызове команды предмет в руке полностью ремонтируется. Команда доступна опнутым игрокам.
     *
     * Использование:
     *
     * /repair,
     * /r
     */

    public static final String
    NAME = "repair",//Имя команды, используется при вызове
    ALIAS = "r",//Допустимая вариация команды, таких может быть несколько
    USAGE = "/repair";//Шаблон вызова, выводится при выбрасывании WrongUsageException

    @Override
    public String getName() {
  
        return this.NAME;
    }

    @Override
    public String getUsage(ICommandSender sender) {
  
        return this.USAGE;
    }

    @Override
    public List<String> getAliases() {
  
        List<String> aliases = new ArrayList<String>();//Так как допустимых вариаций может быть много, передаются они массивом строк
  
        aliases.add(this.ALIAS);
  
        return aliases;
    }

    //Перед выполнением команды происходит вызов этого метода для проверки наличия прав на использование
    public boolean checkPermission(MinecraftServer server, ICommandSender sender) {
  
        return sender instanceof EntityPlayer ? server.getPlayerList().canSendCommands(((EntityPlayer) sender).getGameProfile()) : false;//Если игрок опнут (opped), то он может использовать команду
    }

    //Выполнение команды происходит здесь, этот метод вызывается только на сервере (физическом или логическом)
    @Override
    public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException {
              
        if (sender instanceof EntityPlayer) {
                                      
            if (args.length > 0) {
                                  
                throw new WrongUsageException(this.getUsage(sender));//Выбросить исключение если вместе с командой переданы какие либо аргументы.
            }
      
            EntityPlayer player = this.getCommandSenderAsPlayer(sender);//Получение экземпляра игрока, вызвавшего команду 
                  
            ItemStack mainHandItem = player.getHeldItemMainhand();
      
            TextComponentTranslation chatMessage;//Ссылка на сообщение, которое будет выведено в чат игрока
                                          
            if (mainHandItem != ItemStack.EMPTY || mainHandItem.getItem() != Items.AIR) {
              
                chatMessage = new TextComponentTranslation("commands.repair.item");//Начало сообщения - "Предмет"
              
                chatMessage.getStyle().setColor(TextFormatting.RED);//Установка цвета сообщения
                                  
                TextComponentTranslation itemName = new TextComponentTranslation(" <" + mainHandItem.getDisplayName() + "> ");//Отдельный объект TextComponentTranslation для имени предмета
              
                itemName.getStyle().setColor(TextFormatting.WHITE);
              
                chatMessage.appendSibling(itemName);//Сцепление - "Предмет <Имя> "
              
                if (mainHandItem.isItemStackDamageable()) {
          
                    if (mainHandItem.isItemDamaged()) {
                      
                        mainHandItem.setItemDamage(0);//Если предмет может быть повреждён и имеет повреждение - ремонт
                  
                        chatMessage.getStyle().setColor(TextFormatting.GREEN);
                      
                        chatMessage.appendSibling(new TextComponentTranslation("commands.repair.repaired"));//"Предмет <Имя> отремонтирован"
                    }
                  
                    else {
                      
                        //Если предмет не повреждён
                  
                        chatMessage.appendSibling(new TextComponentTranslation("commands.repair.noDamage"));//"Предмет <Имя> отремонтирован"
                    }
                }
              
                else {
              
                    //Если предмет нельзя повредить
                  
                    chatMessage.appendSibling(new TextComponentTranslation("commands.repair.canNotRepair"));//"Предмет <Имя> не может быть отремонтирован"
                }
            }
          
            else {
          
                //Если предмета в руке нет
              
                chatMessage = new TextComponentTranslation("commands.repair.handEmpty");//"Нет активного предмета"
            }
          
            player.sendMessage(chatMessage);//Вывод сообщения для игрока, вызвавшего команду                 
        }
    }
}

result.png

В моём репозитории так же доступны примеры команд с использованием аргументов и отправкой модифицированного сообщения всем игрокам.

Вот и всё. Если есть вопросы или замечания - поделитесь в обсуждении. Так же если будут пожелания, могу дополнить туториал информацией для других версий.
Автор
AustereTony
Просмотры
5,253
Первый выпуск
Обновление
Оценка
5.00 звёзд 4 оценок

Другие ресурсы пользователя AustereTony

Последние обновления

  1. Обновление #1

    Добавлена информация по созданию команды для версии игры 1.7.10.
Сверху