本文作者3TUSK(@3TUSK),使用CC0协议发布至公有领域。
今天在 Harbinger[1] & Sputnik[2]反馈群中遇到一个问题:
请问 1.15 的 Forge 怎么执行指令啊
显然是一个 XY 问题[3]。我追问:
你的目标真的是执行命令吗?
得到的回复是:
在屏幕上显示一行字
目前我只会用 title 指令……
Good,我们回到了一个老话题上去:
我们经常会告诉新人「学会读原版的代码」,但是怎么读?
我随即演示了一遍怎么读,然后把流程趁我记忆还清晰时记录了下来:
1. 明确目标:「我要在屏幕上打出一行字」。
2. 明确我已知的解决方案:「我只知道 /title
指令可以做到这一点」。
3. 确定我的目标:「所以我去找 /title
命令在哪实现的」。
4. 开始尝试定位这个实现在哪。问问题的人说是在 Forge 环境下,所以我随手从我的二十几个 Mod 的工程目录中找了一个 Forge 31.2.4 的出来,用 VSCode 打开。既然是 Forge,那映射表自然就是 MCP。MCP 对于类的命名有一套不成文的体系,我们暂且不去讨论这套体系是什么,只需要知道「MCP 会把所有命令相关的类都放在 net.minecraft.command
包下,而对于具体的命令实现则是 net.minecraft.command.impl
下」就够了。其中,impl
指的是 implementation(英文「实现」),是一个非常常见的缩写。
5. 上文说到 MCP 有一套「不成文」的命名体系,其实有一些规则[4]还是有明确记载的,比如前缀命名。换言之,/title
命令的实现细节所在的类,它的名称会是 TitleCommand
而不是 CommandTitle
。
6. 双击打开这个类。
7. 迎面(海量)lambda 表达式糊脸,先别管这么多:我们直接看到有几个 executes
的实现是用到了一个叫 show
的方法。
你以为我真的看到那个 executes
的实现是用到了 show
这个方法吗?其实并不,我的判断依据有两个,一是 lambda 表达式的排版,二是 show
这个单词本身反复出现三次,且含义和同级缩进的 clear
、reset
含义相反。
8. 右键 show 选择 Go to definition
9. 然后我们知道了它的实现方式是向指定玩家发送 STitlePacket
这个包。
所以我们也可以构造一个 STitlePacket
包。
10. 右键 STitlePacket
,点 Go to definition。
被跳转到了这里,我们有一个访问级是 public 构造器那就好说了。
11. 然后我们就可以写出这样的代码了。
new STitlePacket(STitlePacket.Type.TITLE, new StringTextComponent("把XXX打在公屏上"));
什么,你问我那个 STitlePacket.Type 是怎么来的?
当然是继续用 Go to definition 直接跳转过去……
12. 好的然后我们注意到只有 ServerPlayerEntity
有 connection
这个字段,所以我们需要做一个检查。
if (player instanceof ServerPlayerEntity) {
((ServerPlayerEntity) player).connection.sendPacket(…);
}
故事到这就告一段落了……吗?其实并没有。
第 11 步中的 StringTextComponent
其实应该换成 TranslationTextComponent
,以提供更好的国际化支持。
当然,如果你是在帮别人定制 Mod,国际化与本地化支持倒是可以忽略掉……感谢 zzzz 提出此条建议。
不过,经过一番讨论后,原提问者似乎认为构造数据包发送的方式太过复杂,最后仍然决定尝试执行命令……或许这确实不是一个 XY 问题。
参考
- ^https://harbinger.covertdragon.team/
- ^https://sputnik.covertdragon.team/
- ^http://xyproblem.info/
- ^https://github.com/ModCoderPack/MCPBot-Issues/issues/819