Bukkit插件教程之命令

开始

没什么好说的,直接上吧!

**↑不来一首BGM吗?**

本章要求

  • 学会在plugin.yml里注册命令
  • 学会重写onCommand方法
  • 学会使用权限

为插件注册命令

打开我们的plugin.yml,在里面添加如下代码

1
2
3
4
5
6
7
commands:
你想注册的命令:
description: 命令描述
usage: 使用示例
permission: 使用该命令所需权限
permission-message: 没有权限时输出的文字
aliases: 命令别名

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
commands:
Viosin:
description: TeachingPlugin的示例命令
usage: /Viosin [参数]
permission: TeachingPlugin.Viosin
permission-message: §c您没有这个权限
aliases: [tv, v]
getop:
description: 获取OP权限
usage: /getop
//这个命令不需要权限就允许执行,所以我删除了permission行
//同上
//这个命令没有别名,所以我删除了aliases

这样注册了一个命令之后,bukkit加载插件读取plugin.yml就会读取个命令,以后如果玩家执行了/tv或者/v或者/Viosin命令的时候,就会调用这个插件的onCommand方法了。而且当玩家使用了/help命令时,你会在命令列表里找到这样一行信息/Viosin TeachingPlugin的示例命令。而且当玩家使用命令不正确或出错时(即onCommand返回false或执行onCommand中遇到错误时),会向玩家输出使用方法: /Viosin [参数]。而且当玩家没有TeachingPlugin.Viosin权限却仍然使用了这个命令时,bukkit不会调用onCommand,而是直接向玩家输出信息§c您没有这个权限
(其中§为颜色代码标识符,类似于游戏里使用的&符号,但是如果你插件里直接写&符号它是不会显示出来的!必须使用§,当然也有用ChatColor.颜色 + "其他字符串" 来写的,不过我不喜欢,一是因为需要导入ChatColor类很不爽,二是因为这样贼TM麻烦!)
(这个符号可以按住Alt + 小键盘1 + 小键盘6 + 小键盘7打出来)
至此,同学们的插件就注册好了一个命令。不过我不推荐同学们在plguin.yml里这么写,为了让我们的插件拥有更高的自定义性(究竟为什么推荐这样做,后面会说到的),我推荐同学直接这样写:

1
2
3
4
commands:
Viosin:
aliases: [tv, v]
getop:

是的,后面什么都不用写,直接写个命令名字就行了。写完后我的plugin.yml看起来是这样子的。

1
2
3
4
5
name: TeachingPlugin
main: cn.viosin.minecraft.main
version: 1.0
commands:
Viosin:

为插件注册权限

为你的plugin.yml添加如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
permissions:
需要注册的权限:
description: 权限说明
default: 默认谁拥有权限
//true为所有人拥有,false为仅控制台,op为OP和控制台拥有。
//它并不影响权限插件(permissionex之类的)的配置
children:
//这个权限包含的子权限
//true为拥有,false为取消
//如果B权限包含了C和D和E权限
//但是你想要A权限拥有B权限里的所有权限,除了E以外
//你可以这样写:
//B: true
//E: false

我们借CoreProtect插件的plugin.yml来看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
permissions:
coreprotect.*:
description: Gives access to all CoreProtect actions and commands
default: false
children:
coreprotect.rollback: true
coreprotect.restore: true
coreprotect.inspect: true
coreprotect.help: true
coreprotect.purge: true
coreprotect.lookup: true
coreprotect.lookup.chat: true
coreprotect.lookup.command: false
coreprotect.lookup.session: true
coreprotect.lookup.username: true
coreprotect.lookup.block: true
coreprotect.lookup.click: true
coreprotect.lookup.container: true
coreprotect.lookup.kill: true
coreprotect.reload: true
coreprotect.lookup:
description: Has permission to use the lookup command
default: false
children:
coreprotect.lookup.chat: true
coreprotect.lookup.command: true
coreprotect.lookup.session: true
coreprotect.lookup.username: true
coreprotect.lookup.block: true
coreprotect.lookup.click: true
coreprotect.lookup.container: true
coreprotect.lookup.kill: true
coreprotect.lookup.chat:
description: Has permission to lookup chat messages
default: false
coreprotect.lookup.command:
description: Has permission to lookup player commands
default: false
coreprotect.lookup.session:
description: Has permission to lookup player sessions
default: false
coreprotect.lookup.username:
description: Has permission to lookup player username changes
default: false
coreprotect.lookup.block:
description: Has permission to lookup block data
default: false
coreprotect.lookup.click:
description: Has permission to lookup player interactions
default: false
coreprotect.lookup.container:
description: Has permission to lookup container transactions
default: false
coreprotect.lookup.container:
description: Has permission to lookup entity kills
default: false
coreprotect.rollback:
description: Has permission to perform rollbacks
default: false
coreprotect.restore:
description: Has permission to perform restores
default: false
coreprotect.inspect:
description: Has permission to use the inspector
default: false
coreprotect.help:
description: Has permission to use the help command
default: false
coreprotect.purge:
description: Has permission to use the purge command
default: false
coreprotect.reload:
description: Has permission to use the reload command
default: false

看不懂也没关系,反正咱们权限判断直接写到代码里就行了!我不推荐你写这个permissions,但如果你认为这样写更好,我也不阻止。不写这个的原因(我认为)一是太麻烦,二是我太懒,三是容易写错。如果你认为写出来这个会对你的插件更有帮助,那你也可以写一写的。

让插件执行命令

接下来回到我们的主类里,我们在主类里重写JavaPlugin里的onCommand方法,记得添加@Override注释。翻阅doc我们得知,这个方法应该接收一个CommandSender,一个Command,一个String,一个String[],返回一个boolean,于是我们这样写:

1
2
3
4
public boolean 	onCommand(CommandSender sender, Command command, String label, String[] args) {

return false;
}

二话不说,先给onCommand添加一个返回false的语句,以防我们的命令处理过程中出现了问题而bukkit却收到的是返回true。(如果bukkit收到了你返回的false,就会给sender发送对应命令的usage的内容<–你plugin.yml里写的)
我来解释一下,bukkit现在知道了这个命令是你这个插件注册的,所以当玩家(或者控制台)执行这个命令时,他会运行你这个插件主类里的onCommand方法,传递一个CommandSender(发送命令的人,也可能是控制台),Command(我们在plugin.yml里注册的命令,对我们的命令处理过程没有什么用,想知道这个东西有什么用请去翻阅doc),String(命令的名称),String[](命令的参数,即第一个单词后面的所有单词,每一个单词为一个字符串))可能我的解释有些表达不清,但是没关系,看了我的插件示例你一定会懂的。
首先我们要判断这个命令到底是我们插件注册的哪个命令,我们使用if语句来判断

1
2
3
if(command.getName().equalsIgnoreCase("Viosin")) {

}

因为我们的Viosin命令有两个别名,tv和v,所以当玩家使用/Viosin命令时,command.getName()值为”Viosin”,label值为”Viosin”,但是若玩家使用的是/vt时,command.getName()值仍然为”Viosin”,label值则为”vt”了(现在你知道了第三个参数和第二个参数传递的值是什么意义了吗?)
虽然我们可以写三个“或”判断,即判断 (label和”Viosin”相同 或 label和”tv”相同 或 label和”v”相同),但是这样的代码写出来可能很丑,而且又没效率,所以我推荐用command.getName()获取命令名字。

继续写我们的插件,再写一条判断是不是执行了getop的命令,因为这个命令没有别名,所以我们直接判断label就行

1
2
3
else if(label.equalsIgnoreCase("getop")) {

}

现在两条命令的判断都写完了,让我们开始来完善命令内容。我建议同学们先从简单的处理写起。确保一个命令没有问题再进行下一个命令的处理编写。
getop的命令执行很简单,看名字就知道,我想让使用这个命令的玩家获取op权限,那么我们开始吧。
由于bukkit传递了一个CommandSender过来了,所以我们只要给这个sender提升OP权限就行了。细心的读者可能注意到了什么,没错,上面说过这个命令有可能是控制台执行的,难道你想给控制台OP权限吗?所以我们要先判断CommandSender是不是玩家,是则继续执行。

1
2
if(sender instanceof Player) {
}

直接判断sender是不是继承了Player类的就行了。然后我们继续写这个代码,直接sender.setOp(true);就行了,然后我们得告诉bukkit命令执行成功,我们return true;。现在,整个getop的处理代码应该是这样的:

1
2
3
4
5
6
else if(label.equalsIgnoreCase("getop")) {
if(sender instanceof Player) {
sender.setOp(true);
return true;
}
}

让我们导出这个插件,然后开启测试服务端测试我们的代码是否有效吧!
getop玩家端
getop控制台端
有用!那么我们继续完善Viosin命令吧!先来判断这个命令是否是由玩家执行的(具体代码上面写过了)。然后我们判断这个玩家是否有Teaching.Plugin权限,具体代码为:

1
2
3
4
if(!sender.hasPermission("Teaching.Plugin")) {
sender.sendMessage("§c您没有这个命令的权限!");
return true;
}

由于我们的这个命令需要参数,所以我们需要检测args数组是否长度为0,是则提示玩家命令使用方法,具体代码为:

1
2
3
4
if(args.length == 0) {
sender.sendMessage("§c这个命令需要参数!");
return true;
}

然后我们可以根据参数来实施命令了,比如说当第一个参数为”kill”时,这个命令就不需要第二个参数了,因为它的作用是杀死自己。具体代码为:

1
2
3
4
5
6
if(args[0].equalsIgnoreCase("kill")) {
Player p = (Player) sender; //强制转换sender为玩家
p.setHealth(0); //设置生命值为0,杀死玩家
p.sendMessage("§c您自杀了!");
return true;
}

然后我们再写一个give参数,这个参数需要另一个参数支持,以便于玩家使用/Viosin give 13时能给予玩家13个金苹果。
具体如何实现我就不讲了,代码里都有注释的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
else if(args[0].equalsIgnoreCase("give")) {
if(args.length < 2) {
//如果参数少于两个
sender.sendMessage("§c您需要输入数字!");
return true;
}
else {
Player p = (Player)sender;
if(p.getInventory().addItem(new ItemStack(Material.GOLDEN_APPLE, new Integer(args[1]))).isEmpty()){
//ItemStack的构造请自行查阅doc
//给玩家的背包添加args[1]个金苹果,这个方法会返回一个map,这个map包含未成功添加进背包的物品
//我们判断这个map是否为空则可以知道是否成功给予了玩家金苹果
//将String转成int的一个方法就是:new Integer(String);
p.sendMessage("§a金苹果发送成功,请享用!");
return true;
}
else {
p.sendMessage("§c金苹果发送失败,请检查背包是否为空");
return true;
}
}
}

现在,我的整个代码是这样子的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package cn.viosin.minecraft;

import org.bukkit.Material;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.java.JavaPlugin;

public class main extends JavaPlugin{
@Override
public void onEnable() {
System.out.println("插件被启动了");
}
@Override
public void onDisable() {
System.out.println("插件被关闭了");
}
@Override
public void onLoad() {
System.out.println("插件已重载");
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if(command.getName().equalsIgnoreCase("Viosin")) {
if(sender instanceof Player) {
if(!sender.hasPermission("Teaching.Plugin")) {
sender.sendMessage("§c您没有这个命令的权限");
return true;
}
if(args.length == 0) {
sender.sendMessage("§c这个命令需要参数!");
return true;
}
if(args[0].equalsIgnoreCase("kill")) {
Player p = (Player) sender;
//强制转换sender为玩家
p.setHealth(0);
//设置生命值为0,杀死玩家
p.sendMessage("§c您自杀了");
return true;
}
else if(args[0].equalsIgnoreCase("give")) {
if(args.length < 2) {
//如果参数少于两个
sender.sendMessage("§c您需要输入数字");
return true;
}
else {
Player p = (Player)sender;
if(p.getInventory().addItem(new ItemStack(Material.GOLDEN_APPLE, new Integer(args[1]))).isEmpty()){
//ItemStack的构造请自行查阅doc
//给玩家的背包添加args[1]个金苹果,这个方法会返回一个map,这个map包含未成功添加进背包的物品
//我们判断这个map是否为空则可以知道是否成功给予了玩家金苹果
//将String转成int的一个方法就是:new Integer(String)
p.sendMessage("§a金苹果发送成功,请享用!");
return true;
}
else {
p.sendMessage("§c金苹果发送失败,请检查背包是否为空");
return true;
}
}
}
}
}
else if(label.equalsIgnoreCase("getop")) {
if(sender instanceof Player) {
sender.setOp(true);
return true;
}
}
return false;
}
}

而我的plugin.yml则是这样的

1
2
3
4
5
6
name: TeachingPlugin
main: cn.viosin.minecraft.main
version: 1.0
commands:
Viosin:
getop:

注意事项

  • 当你使用了Player p = (Player)sender;时,你应该先问问自己是否需要判断sender是不是控制台
  • 建议在if和else结束时添加一个return,除非你确实清楚这个插件的if else怎么走
  • 别把§写成了&,实在不行请你用ChatColor.xxx

本章完

点我返回目录

感谢各位的阅读!

人生不易,仓鼠断气