MC模组服务端代码测试
在MC模组开发过程中,很多时候我们都需要测试代码能否在服务器上运行,而Forge等开发平台也都提供了这样的工具,本文便就是介绍如何使用这些工具测试我们的模组能不能在服务端加载
起因
在开发我的第一个模组(也是第一个编程项目)SmarterContraptionStorage时,我的模组经常出现服务器无法加载的情况 而且一查代码都是很愚蠢的客户端代码写在服务端导致的错误,因此我才重视起来在服务端测试我的代码的事情。
起初我以为服务端代码测试要求我先架设一台服务器,而我并不会架设服务器,所以我根本就没有测试代码能不能在服务端跑。不过在学习之后(首先学习的是Forge上如何测试)我发现并不需要那么麻烦,ForgeGradle已经提供了测试的Gradle Task,我只需要修改一些基本配置并运行,一台临时的服务器就开启起来了。下面我就将我的学习结果记录在下面,包含Forge和NeoForge上进行服务端代码测试的方法
Forge
工具
Forge提供了两个和服务器有关的测试Task,分别是runServer
和runGameTestServer
,其中runServer
可以持续地开启一个临时的服务器,还允许别人加入。而runGameTestServer
功能相对更简单,更适合Github Action这种定时任务检查代码,其会开启一个服务器,并进入一个预先设置好的世界,成功之后便自动退出,并不允许其他玩家加入和细节测试。所以我们主要针对的就是runServer
任务。
更改配置
但是要注意的是你如果直接允许这个任务而不修改任何配置是会无法进入服务器的,它会直接退出,而且还会返回SUCCESS
让你以为执行其实成功了,但是实际上没有打开服务器。这主要是因为你没有同意run/eula.txt
1里的Minecraft 的终端用户许可协议(EULA),所以按照设计自己立即退出了,而Gradle以为执行成功,返回SUCCESS
误导你以为没问题。你只需要进入run/eula.txt
1将eula=false
改为eula=true
然后再次运行命令就可以打开服务器了。
但是就算你打开了服务器,你此时也进不去游戏,因为服务器的一些基本设置我们还没有更改,这些条目和开一个服务器需要更改的条目是一样的,所以同样地更改就可以了,在我测试的过程中我总结的以下的条目最需要更改。
首先最需要更改的是服务器设置,文件是run/server.properties
1,其中我们需要更改连接的玩家的限制,允许离线账号登录,否则runClient
启动的游戏是无法登录进服务器的。具体地是online-mode
我们需要设置为false
,设置之后我们就可以加入服务器了。
其次我们还需要给自己op权限以使用命令,对应的文件是run/ops.json
1,我们需要将我们自己的玩家加进文件中,比如:
1
2
3
4
5
6
7
8
[
{
"name":"Dev",
"uuid":"380df991-f603-344c-a090-369bad2a924a",
"level": 4,
"bypassesPlayerLimit": true
}
]
其中name
是玩家名字,uuid
则是玩家对应的UUID,这两个需要相互对应,获取其最简单的方法就是先用runClient
启动的游戏登录一次临时的服务器,然后在run/usercache.json
1中就能找到对应的登录过的玩家的信息(正常情况下你更改登录过的那一个记录),将数据复制过去即可。后面的level
是玩家的op权限等级,最高是4,所以写4即可,第四个bypassesPlayerLimit
是是否允许管理员无视服务器人数上限加入游戏,true
表示无视,这个对我们无关紧要,随便写就可以。
加入游戏
在所有这些配置之后,我们就可以让Idea同时运行runServer
和runClient
命令,开启临时服务器和一个客户端,然后在客户端中正常按多人游戏联机就可以加入我们临时的服务器了,其中网址填localhost:25565
,因为Minecraft服务器默认启动在25565端口上,如果更改了端口会导致网址的改变,这个就看你具体设置的那个端口了。
其他
还有一些其他的点我没有提到或者我没有找到,那就是我并没有找到runServer
开启的临时服务器的控制台,从而没有办法使用控制台命令,对此我不清楚到底是没有控制台还是我没有找到,还请知道的大佬评论区补充🙏。
NeoForge
对于NeoForge,其与Forge几乎相同,因为他们毕竟是同根生的,仍然是runServer
和runGameTestServer
这两个选项,设置也几乎完全一样,文件run/server.properties
1和run/ops.json
1都是一样的,修改也完全一样。但是也有不一样的地方,比如NeoForge没有run/eula.txt
1文件,这可以说是非常好了,这样就不存在误以为运行成功了的情况了;而且即使不更改run/server.properties
1文件,在runClient
开启的游戏中,我们仍然可以进得去服务器。
其他
值得说道的是NeoForge服务器和客户端启动都比Forge更快,不愧是优化过的。👍
此外我在运行runGameTestServer
时遇到了运行失败的问题,原因是机械动力有自定义的需要在runGameTestServer
时运行的测试,而我并没有包含其导致失败,我的解决办法很奇怪,我发现我的build.gradle
文件最开始写的是
1
2
3
4
5
runs{
gameTestServer {
systemProperty 'forge.enabledGameTestNamespaces', project.mod_id
}
}
这导致设置为只加载我的自定义的需要测试的东西,而机械动力的未加载。可是我看到这里写的是forge
,所以我尝试把它改成neoforge
,然后竟然能成功运行了。我猜测是因为NeoForge开发环境使用neoforge
会自动检测依赖的测试目标吧,不是很清楚,在此仅做记录。
Bug
在测试1.20.1支持机械动力6.0.0版本的代码时,我遇到了一个奇怪的问题,runClient
执行完全正常没有问题,可是一旦执行runServer
就报错,信息如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[07:37:18] [Worker-Main-11/INFO] [minecraft/LoggerChunkProgressListener]: Preparing spawn area: 61%
[07:37:19] [Server thread/INFO] [minecraft/LoggerChunkProgressListener]: Time elapsed: 5836 ms
[07:37:19] [Server thread/INFO] [minecraft/DedicatedServer]: Done (7.645s)! For help, type "help"
[07:37:19] [Server thread/ERROR] [ne.mi.fm.lo.RuntimeDistCleaner/DISTXFORM]: Attempted to load class net/minecraft/client/server/LanServerPinger for invalid dist DEDICATED_SERVER
[07:37:19] [Server thread/ERROR] [minecraft/MinecraftServer]: Encountered an unexpected exception
java.lang.RuntimeException: Attempted to load class net/minecraft/client/server/LanServerPinger for invalid dist DEDICATED_SERVER
at net.minecraftforge.fml.loading.RuntimeDistCleaner.processClassWithFlags(RuntimeDistCleaner.java:57) ~[fmlloader-1.20.1-47.4.0.jar:1.0] {}
at cpw.mods.modlauncher.LaunchPluginHandler.offerClassNodeToPlugins(LaunchPluginHandler.java:88) ~[modlauncher-10.0.10.jar:?] {}
at cpw.mods.modlauncher.ClassTransformer.transform(ClassTransformer.java:120) ~[modlauncher-10.0.10.jar:?] {}
at cpw.mods.modlauncher.TransformingClassLoader.maybeTransformClassBytes(TransformingClassLoader.java:50) ~[modlauncher-10.0.10.jar:?] {}
at cpw.mods.cl.ModuleClassLoader.readerToClass(ModuleClassLoader.java:113) ~[securejarhandler-2.1.10.jar:?] {}
at cpw.mods.cl.ModuleClassLoader.lambda$findClass$15(ModuleClassLoader.java:219) ~[securejarhandler-2.1.10.jar:?] {}
at cpw.mods.cl.ModuleClassLoader.loadFromModule(ModuleClassLoader.java:229) ~[securejarhandler-2.1.10.jar:?] {}
at cpw.mods.cl.ModuleClassLoader.findClass(ModuleClassLoader.java:219) ~[securejarhandler-2.1.10.jar:?] {}
at cpw.mods.cl.ModuleClassLoader.loadClass(ModuleClassLoader.java:135) ~[securejarhandler-2.1.10.jar:?] {}
at java.lang.ClassLoader.loadClass(ClassLoader.java:520) ~[?:?] {}
at net.minecraft.server.dedicated.DedicatedServer.initServer(DedicatedServer.java:197) ~[forge-1.20.1-47.4.0-merged.jar%23145!/:?] {re:classloading,pl:accesstransformer:B}
at net.minecraft.server.MinecraftServer.runServer(MinecraftServer.java:640) ~[forge-1.20.1-47.4.0-merged.jar%23145!/:?] {re:mixin,pl:accesstransformer:B,re:classloading,pl:accesstransformer:B,pl:mixin:A}
at net.minecraft.server.MinecraftServer.lambda$spin$2(MinecraftServer.java:251) ~[forge-1.20.1-47.4.0-merged.jar%23145!/:?] {re:mixin,pl:accesstransformer:B,re:classloading,pl:accesstransformer:B,pl:mixin:A}
at java.lang.Thread.run(Thread.java:842) ~[?:?] {}
在生成完成地图之后报错在服务端加载了一个客户端类LanServerPinger
,这个问题整整折磨了我一天,并且完全没有头绪,我完全想不通是我哪里出了问题,我调整我的环境配置、更改我的代码都无法解决这个问题。而且最神奇的是就算我把我的代码都去掉,它仍然会报错。在整整折磨了一天之后,我看到一个Issue,我才知道,怪不得我调半天都不行,原来搁着是NeoForge的问题啊😭👊,是说我不论怎么调整我的配置都完全不能用。主要是一个新手确实也不太敢相信是NeoForge有问题,不过解决起来倒是很简单,因为我期望的是同时支持Forge和NeoForge,所以使用的是legacyforge,而这里只需要更改其版本到2.0.84及以上就可以了,如下:
1
2
3
plugins{
id "net.neoforged.moddev.legacyforge" version "2.0.84"
}
然后再运行runServer
,问题就解决了。