本课作品
带参数检查的 /kit 工具包
一条 /kit 命令先经过调用者、权限与参数检查,才把物品放进背包。你将同时照顾玩家、控制台和满背包情况,让失败也得到清楚的回应。 前置背包:第31课的Gradle项目与Paper测试服。复制项目为BlockAcademyL32,保留构建脚本,将settings中的项目名改为BlockAcademyL32。
开始前:建议先完成第 31 课
01 / 13
开始前:先认清运行位置
前30课打好了普通 Java 程序的基础。这一阶段把代码接到 Paper 服务器:Minecraft 客户端负责进入世界,服务器加载插件,事件和命令把玩家动作交给你的方法。
本书固定使用 JDK 21 与 Paper 1.21.11,客户端版本也保持一致。下载页面上的最新版本可能已经变了,按版本号选择。本书采用普通 plugin.yml 与 Bukkit API;本阶段没有声称支持 Folia。
图框中的短例是局部片段,先读本节的位置说明,不要各自保存成插件。完整文件会明确给出文件名;同一文件分成多段时,按编号连接,包名、import 与外层类只出现一次。
- 预测:先写成功条件和预期消息。
- 运行:构建成功后,在专用本机测试服安装并重启。
- 核对:普通玩家、管理身份与控制台分别走对应路径。
02 / 13
先认清是谁在发命令
村民在控制台输入领工具包命令,程序却找他要背包。控制台没有手,更没有快捷栏。命令第一关先认清调用者,再数参数,最后才打开配方箱。
把命令当作一条有门卫的路线:调用者类型、权限、参数数量、参数内容都通过后,才修改背包。失败也是一次需要清楚回复的处理,别让玩家只看到沉默。
- 玩家能领取到背包
- 控制台得到明确拒绝
- 共同规则检查完毕才改变物品
安全取得玩家 · 位置见本节说明
if (!(sender instanceof Player player)) {
sender.sendMessage("请在游戏里领取工具包");
return true;
}
player.sendMessage(Component.text("背包主人确认完毕"));错误的强制转换 · 故意错误,先预测再修复
Player player = (Player) sender; // 控制台执行会出错03 / 13
先数参数再打开格子
输入 /kit miner 时,kit 是命令名,args 里只有 miner;args[0] 不是 kit。玩家只敲 /kit 时数组长度是0,此时读取第0格就越界。
- 先数参数长度必须为1
- 再读第0格它是miner,不是kit
- 最后认配方未知选项明确拒绝
沿箭头进入下一步前,必须满足前一步条件;被拒绝的分支提前返回。
只接受一个参数 · 位置见本节说明
if (args.length != 1) {
player.sendMessage(Component.text("用法:/kit miner 或 /kit builder"));
return true;
}不受系统语言影响的比较 · 位置见本节说明
String name = args[0].toLowerCase(Locale.ROOT);| 输入 | 长度 | 结果 |
|---|---|---|
| /kit | 0 | 显示用法 |
| /kit miner | 1 | 选择矿工包 |
| /kit miner extra | 2 | 显示用法 |
| /kit wizard | 1 | 提示未知工具包 |
04 / 13
命令清单和权限要配套
建立 lesson32 包,主类叫 KitPlugin,替换资源目录中的 plugin.yml。练习默认允许玩家领取,代码仍检查权限,方便以后由权限管理系统收回某个玩家的资格。
- 命令kit清单与getCommand对应
- 权限节点代码在执行前检查
- 默认规则本章default为true
本课完整 plugin.yml · 完整文件
name: BlockAcademyL32
version: 1.0.0
main: de.teacherma.lesson32.KitPlugin
api-version: '1.21.11'
author: TeacherMa
website: https://www.teacher-ma.de
commands:
kit:
description: 领取课堂工具包
usage: /kit <miner|builder>
permissions:
blockacademy.kit:
default: true执行前查权限 · 位置见本节说明
if (!player.hasPermission("blockacademy.kit")) {
player.sendMessage(Component.text("你还没有领取权限"));
return true;
}05 / 13
背包满了物品去哪儿
addItem 尝试把物品塞进背包,并返回没有放进去的部分。返回的 Map 记录剩余物品,键是输入物品的序号,不是背包槽位。不能只说“领取成功”就把剩余物品忘掉。
- 尝试入包addItem修改背包
- 取得剩余返回Map里的物品
- 如实处理掉在脚边并提示
发物品并处理剩余 · 位置见本节说明
var leftovers = player.getInventory().addItem(
new ItemStack(Material.TORCH, 16));
for (ItemStack item : leftovers.values()) {
player.getWorld().dropItemNaturally(player.getLocation(), item);
}反馈要符合结果 · 位置见本节说明
String message = leftovers.isEmpty()
? "全部放入背包" : "剩余物品掉在脚边";
player.sendMessage(Component.text(message));06 / 13
按 Tab 给玩家一点提示
命令补全只推荐可能的输入,真正执行时仍要检查参数。否则玩家手动输入一个奇怪名字,就能绕过只存在于菜单里的规则。
- 补全提示只推荐合法名字
- 执行验证手动输入仍需检查
- 未知前缀返回空列表
从列表筛选补全项 · 位置见本节说明
String prefix = args[0].toLowerCase(Locale.ROOT);
return List.of("miner", "builder").stream()
.filter(name -> name.startsWith(prefix)).toList();不要访问不存在的参数 · 位置见本节说明
if (args.length != 1) return List.of();
if (!sender.hasPermission("blockacademy.kit")) return List.of();| 当前参数 | 推荐 |
|---|---|
| 空字符串 | miner 和 builder |
| m | miner |
| b | builder |
| z | 空列表 |
07 / 13
工具包配方先写在纸上
今天的 挑战 是两种工具包。先把配方写清,再把输入、判断、发放和反馈串起来。练习版可重复领取;冷却功能会在第34课学会后再加。
- miner石镐1把、火把16个
- builder木板32个、玻璃16个
- 未知名字返回null后停止发放
用 switch 选配方 · 位置见本节说明
ItemStack[] items = switch (name) {
case "miner" -> new ItemStack[] {
new ItemStack(Material.STONE_PICKAXE),
new ItemStack(Material.TORCH, 16)};
default -> null;
};| 工具包 | 物品一 | 物品二 |
|---|---|---|
| miner | 石镐 1把 | 火把 16个 |
| builder | 橡木木板 32个 | 玻璃 16个 |
08 / 13
工具包程序上半部
新建 src/main/java/de/teacherma/lesson32/KitPlugin.java。先复制本页,再在后面紧接下一页;中间不加包名、import 或额外的大括号。
- 一份源文件前后两部分顺序连接
- 先检查类型、权限和参数
- 通过后才进入配方与发放
KitPlugin 第1部分 · 本段1/2,顺序连接
package de.teacherma.lesson32;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import net.kyori.adventure.text.Component;
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 final class KitPlugin extends JavaPlugin {
@Override
public void onEnable() {
var kit = Objects.requireNonNull(getCommand("kit"), "请检查 plugin.yml");
kit.setExecutor(this);
kit.setTabCompleter(this);
}
@Override
public boolean onCommand(CommandSender sender, Command command,
String label, String[] args) {
if (!(sender instanceof Player player)) {KitPlugin 第1部分 · 本段2/2,顺序连接
sender.sendMessage("请在游戏里领取工具包");
return true;
}
if (!player.hasPermission("blockacademy.kit")) {
player.sendMessage(Component.text("你还没有领取权限"));
return true;
}
if (args.length != 1) {
player.sendMessage(Component.text("用法:/kit miner 或 /kit builder"));
return true;
}09 / 13
工具包程序下半部
下面紧接上一页。发放、剩余物品和补全都放在同一个类里。保持两个 @Override 方法的边界,大括号数量别让苦力怕替你数。
- 发放addItem并处理剩余
- 反馈说明实际结果
- 补全按当前前缀过滤
KitPlugin 第2部分 · 本段1/2,顺序连接
ItemStack[] items = switch (args[0].toLowerCase(Locale.ROOT)) {
case "miner" -> new ItemStack[] {
new ItemStack(Material.STONE_PICKAXE),
new ItemStack(Material.TORCH, 16)};
case "builder" -> new ItemStack[] {
new ItemStack(Material.OAK_PLANKS, 32),
new ItemStack(Material.GLASS, 16)};
default -> null;
};
if (items == null) {
player.sendMessage(Component.text("只认识 miner 和 builder"));
return true;
}
var leftovers = player.getInventory().addItem(items);
for (ItemStack item : leftovers.values()) {
player.getWorld().dropItemNaturally(player.getLocation(), item);
}
player.sendMessage(Component.text(leftovers.isEmpty()
? "工具包已放入背包" : "背包已满,剩余物品掉在脚边"));
return true;
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command,
String alias, String[] args) {KitPlugin 第2部分 · 本段2/2,顺序连接
if (!sender.hasPermission("blockacademy.kit") || args.length != 1)
return List.of();
String prefix = args[0].toLowerCase(Locale.ROOT);
return List.of("miner", "builder").stream()
.filter(name -> name.startsWith(prefix)).toList();
}
}10 / 13
把失败路径也走一遍
先腾出背包做成功测试,再主动制造空参数、陌生参数和满背包。测试的目标是知道每种输入会发生什么,不能只凭“我领到了一次镐子”就宣布通关。
- 正常两种包都符合配方
- 边界零参数、满背包
- 拒绝未知选项、控制台、无权限
| 操作 | 预期 |
|---|---|
| 玩家 /kit miner | 1把石镐和16个火把 |
| 玩家 /kit builder | 32木板和16玻璃 |
| 控制台 kit miner | 提示在游戏里领取,无异常 |
| /kit 或 /kit wizard | 清楚反馈,不发物品 |
| 背包填满后领取 | 脚边有剩余物品,消息准确 |
| 输入 /kit m 后按Tab | 补全miner |
11 / 13
让命令更容易维护
当命令越来越多,每个分支都写一段巨大的发放代码,修一次就像挖整座山。把配方提成方法,命令方法只负责检查和调用。
停下来想一想:读取 args[0] 之前,应该先完成什么判断?零参数为何不能靠后面的配方分支补救?
- 职责分明命令负责检查
- 配方方法只返回物品清单
- 测试同步改规则也改预期
提取一个配方方法 · 位置见本节说明
private ItemStack[] minerKit() {
return new ItemStack[] {
new ItemStack(Material.STONE_PICKAXE),
new ItemStack(Material.TORCH, 16)};
}帮助消息也要一致 · 位置见本节说明
player.sendMessage(Component.text("用法:/kit miner 或 /kit builder"));
// plugin.yml 中 usage 写成同样的选择范围- 把大小写改成 MINER,预期仍识别矿工包。
- 把未知选项改为 wizard,预期没有任何物品变化。
- 把工具包数量改成配方表中的新值,再更新验收记录。
12 / 13
拓展挑战与自己的验收记录
留下源代码、实际运行结果和一条问题记录。先用自己的话解释,再运行验证;结果与预测不同的地方,往往最值得讨论。
- 木镐实现名字问候
- 铁镐完成两种包与失败记录
- 钻石镐按玩家增加独立冷却
- 木镐:实现 /hello,向玩家发送带名字的问候;控制台也能得到合理提示。
- 铁镐:完成两种工具包,提交空参数和满背包的测试记录。
- 钻石镐:学完第34拓展给领取命令加入每人30秒冷却,并验证两名玩家互不影响。
- 验收要求:用玩家和控制台分别测试,补齐无权限、零参数、未知配方、满背包场景;确认失败不会发物品。
| 我能独立完成 | 已掌握 | 再练一次 |
|---|---|---|
| 先检查发送者和参数 | □ | □ |
| 能处理背包剩余物品 | □ | □ |
| 能解释权限与补全 | □ | □ |
13 / 13
把证据留给下一次冒险
把“能编译”“数字规则通过”和“游戏实际表现正确”分成三栏。没有运行的场景写待测;发现 Bug 时记录触发条件、预期、实际、最小修复与复测结果。
| 类别 | 你选择的操作 | 留下的证据 |
|---|---|---|
| 正常 | 本课的一次成功路径 | 版本与实际输出 |
| 边界 | 结束前一刻、最大值或满背包 | 与合同逐项比较 |
| 拒绝 | 错误参数、无权限或不相关事件 | 反馈清楚,状态未误改 |
- 解释本课每条箭头为什么从这里指向那里;并列卡片没有先后含义。
- 先改一个规则,再同时修改说明和预期,用同一输入复测。
TAKE IT WITH YOU
下载本课 PDF
完整课文、代码、图解与练习,选择你需要的语言。
