系统命令的意义

看起来,系统命令这种扩展,似乎只有服务端的插件才有做的必要,其实不然。这里,我们假设有一个Mod,被用于服务端,那么服务器的管理员该如何管控呢?

我们设想这样一个情况,如果这个Mod具有复杂的任务树,那么服务器的管理员可能就存在通过一种方式让玩家一下子跳到某一个任务节点的需要。

我们再设想,如果这个Mod给玩家添加了一个属性值,那个对于服务器的管理员来说,获取并试图修改这个属性值,可能就是一件十分必要的事情。

那么而这样的事情该如何做到呢?显然系统命令是最适合的,通过敲指令来达到这种目的往往是服务器的管理员最擅长的事情,同时,通过命令方块往往能达到自动化的操作。

目标

我们这部分将要带领读者一步一步地制作一个系统命令,这里的示例命令名称为position,用途是显示一个玩家的位置。

  • 当输入命令:/position的时候显示自己的位置
  • 当输入命令:/position Player的时候显示特定玩家Player的位置
  • 当输入命令:/position @a的时候显示所有玩家的位置。

新建一个系统命令

这里,我们要用到一个之前讲到过的FML生命周期事件:FMLServerStartingEvent

src/main/java/com/github/ustc_zzzz/fmltutor/FMLTutor.java(部分):

    @EventHandler
    public void serverStarting(FMLServerStartingEvent event)
    {
        proxy.serverStarting(event);
    }

src/main/java/com/github/ustc_zzzz/fmltutor/common/CommonProxy.java(部分):

    public void serverStarting(FMLServerStartingEvent event)
    {

    }

我们注意到FMLServerStartingEvent有一个名为registerServerCommand的方法,显而易见,我们需要通过调用这个方法来完成系统命令的注册。

registerServerCommand方法需要一个实现了接口ICommand的对象,还好Minecraft这个游戏已经准备好了一个接口ICommand的方法大部分已经实现了的类:CommandBase

新建包com.github.ustc_zzzz.fmltutor.command在其中新建文件CommandPosition.java,并使CommandPosition类继承CommandBase类:

src/main/java/com/github/ustc_zzzz/fmltutor/command/CommandPosition.java:

package com.github.ustc_zzzz.fmltutor.command;

import net.minecraft.command.CommandBase;
import net.minecraft.command.CommandException;
import net.minecraft.command.ICommandSender;

public class CommandPosition extends CommandBase
{
    @Override
    public String getCommandName()
    {
        return "position";
    }

    @Override
    public String getCommandUsage(ICommandSender sender)
    {
        return "commands.position.usage";
    }

    @Override
    public void processCommand(ICommandSender sender, String[] args) throws CommandException
    {

    }
}

我们注意到,对于ICommand接口,CommandBase类还有三个方法没有实现,当然,这三个方法的内容其实都非常简单:

  • getCommandName方法就是这个命令的名称,也就是一个斜线之后紧接着出现的那个。
  • getCommandUsage方法就是这个命令的用法,当玩家输入/help position的时候就会出现。这里自然需要国际化,随后我们就会在语言文件中添加相应字段。
  • processCommand方法的含义更加显而易见,就是这个命令执行的时候会调用的方法。这里的args参数指的是去掉命令名称本身之后剩下的参数,比如如果我们输入/position alice bob carolargs就会是以alicebobcarol为顺序的数组,如果我们只输入/position命令本身,那么args数组就是空的。

processCommand方法可能会抛出CommandException异常,这是因为在执行命令的时候可能会出现各种各样的错误,比如请求的玩家不存在、需要整数的地方提供了一个浮点数、等等,这时候就应该直接抛出异常,阻止命令的继续执行。

CommandBase类提供了很多静态方法,用于一些常见的命令操作,比如通过玩家名称取出对应的玩家实体、将字符串转换成整数、等等,开发者可以放心大胆地使用。这个部分使用了三个这样的方法:

  • getPlayer方法用于通过玩家名称获取对应的玩家实体,如果无法找到,则会抛出异常。
  • getCommandSenderAsPlayer方法用于获取输入该命令的玩家,如果该命令是命令方块等非玩家实体执行的,则会抛出异常。
  • getListOfStringsMatchingLastWord方法用于将当前输入的字符串匹配对应字符串数组中对应的字符串列表,常用于自动补全。

更多方法的使用,只要参照原版的命令来模仿就可以了。

我们这里完成processCommand方法:

src/main/java/com/github/ustc_zzzz/fmltutor/command/CommandPosition.java(部分):

    @Override
    public void processCommand(ICommandSender sender, String[] args) throws CommandException
    {
        if (args.length > 1)
        {
            throw new WrongUsageException("commands.position.usage");
        }
        else
        {
            EntityPlayerMP entityPlayerMP = args.length > 0 ? CommandBase.getPlayer(sender, args[0])
                    : CommandBase.getCommandSenderAsPlayer(sender);
            Vec3 pos = entityPlayerMP.getPositionVector();
            sender.addChatMessage(new ChatComponentTranslation("commands.position.success", entityPlayerMP.getName(),
                    pos, entityPlayerMP.worldObj.provider.getDimensionName()));
        }
    }

代码的意思很简单,就是取出第一个参数对应的玩家实体,如果这个参数不存在就取自己,把其坐标和所在世界输出出来。如果提供的参数不正确,就会抛出异常。

我们现在补充一下语言文件:

src/main/resources/assets/fmltutor/lang/en_US.lang(部分):

commands.position.usage=/position [player]
commands.position.success=The position of %1$s is %2$s in world %3$s

src/main/resources/assets/fmltutor/lang/zh_CN.lang(部分):

commands.position.usage=/position [玩家]
commands.position.success=玩家 %1$s 处于名为 %3$s 的世界,其坐标为 %2$s

除了上面CommandBase未实现的方法,还有一个方法往往需要覆写,这就是getRequiredPermissionLevel方法,这个方法返回执行该命令所需要的等级。

等级一共分四种,对应的数字为1、2、3和4,等级为1代表任何玩家都可以执行,比如/ping这样的命令,等级为2代表命令方块可以执行,而等级4,则只有这个服务器的OP、还有单人模式下的作弊玩家可以执行。

这里我们把等级设置成2:

src/main/java/com/github/ustc_zzzz/fmltutor/command/CommandPosition.java(部分):

    @Override
    public int getRequiredPermissionLevel()
    {
        return 2;
    }

然后我们注册这个命令,在包com.github.ustc_zzzz.fmltutor.command下新建文件CommandLoader.java

src/main/java/com/github/ustc_zzzz/fmltutor/command/CommandLoader.java:

package com.github.ustc_zzzz.fmltutor.command;

import net.minecraftforge.fml.common.event.FMLServerStartingEvent;

public class CommandLoader
{
    public CommandLoader(FMLServerStartingEvent event)
    {
        event.registerServerCommand(new CommandPosition());
    }
}

CommonProxy中完成注册:

src/main/java/com/github/ustc_zzzz/fmltutor/common/CommonProxy.java(部分):

    public void serverStarting(FMLServerStartingEvent event)
    {
        new CommandLoader(event);
    }

就可以了。

最后补充一点,@p@a这样的通配符在Minecraft执行这个命令之前就已经展开成特定的名称了,比如这个服务器有AliceBob两个人,现在执行命令/position @a,相当于执行了一次/position Alice和一次/position Bob,所以这方面是不需要开发者操心的。

打开游戏试试吧~

命令的自动补全

很明显,没有自动补全的命令行界面,无论什么情况下(包括cmd),都是很难用的,所以这里我们理所应当地应该提供自动补全的功能。

当然,实现自动补全的方法也并不难,这里只要实现ICommandaddTabCompletionOptions方法(也就是覆写CommandBase的对应方法)就可以了:

src/main/java/com/github/ustc_zzzz/fmltutor/command/CommandPosition.java(部分):

    @Override
    public List<String> addTabCompletionOptions(ICommandSender sender, String[] args, BlockPos pos)
    {
        if (args.length == 1)
        {
            String[] names = MinecraftServer.getServer().getAllUsernames();
            return CommandBase.getListOfStringsMatchingLastWord(args, names);
        }
        return null;
    }

这里也就是说当玩家输入第一个参数的部分内容,比如/position Ali,或者仅仅输入了一个空格的时候,这个方法会让系统会找到所有这个服务器上的玩家,并且把对应的提供给系统。

上面的例子中,如果服务器中有一个名为Alice的玩家,命令行界面可能就会自动补全成/position Alice

results matching ""

    No results matching ""