前景需要
最近在研究 Minecraft 服务端插件开发,有一个场景需要用到服务器重启时重置地图
搜了一圈好像并没有什么现有的尽人意的插件,于是决定自己写一个
探索
最开始想直接用 Slime World Manager 的,但是一看仓库
This repository was archived by the owner on Apr 21, 2025. It is now read-only.
呃呃,不过恰好又看到它的一个衍生 fork
https://github.com/InfernalSuite/AdvancedSlimePaper
看起来很有用,不过它的插件版好像不太喜欢我的服务器(Paper 1.20.4 + Java 24)
反正就报缺类
但是呢他们好像又起了个分支去魔改 Paper 了,我不是很喜欢
没办法了,直接用土方法
实现
我们都知道 Bukkit API 提供了 onEnable 和 onDisable
原本想的是直接在 onEnable 的时候备份还原一气呵成,但是 Bukkit 的插件加载是比 world 后的
而且我们不能通过 Bukkit API 直接卸载默认世界(也就是 world、world_nether、world_end)
那咋办,其实还有一个 onLoad 可以用,它会领先于地图载入阶段就执行,所有我们就可以愉快的在这里操作地图啦
大致如下
private void copyWorld(Path source, Path target) throws IOException {
Files.walkFileTree(source, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
Path targetDir = target.resolve(source.relativize(dir));
Files.createDirectories(targetDir);
return FileVisitResult.CONTINUE;
}
});
}
然后在你的 backupWorld 逻辑处调用 copyWorld(worldDir, backupWorldDir);
其实你还可以顺手写个校验
private void saveWorldHash(Path world, Path hashFile) throws IOException {
TreeMap<String, String> fileHashes = calculateDirectoryHash(world);
try (BufferedWriter writer = Files.newBufferedWriter(hashFile, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
for (var entry : fileHashes.entrySet()) {
writer.write(entry.getKey() + ":" + entry.getValue() + "\n");
}
}
}
同样在你的逻辑处调用 saveWorldHash(backupWorldDir, hashFile);
欸你问我 world 被加载后那个 session.lock 被挂锁了没法复制?那我们跳过不就好了吗!
然后在恢复的时候我们直接挂到 onLoad 上
然后你已经会写这个逻辑了,快去写个插件吧!
注意事项
不推荐把 backupWorld 的逻辑挂在 onDisable 上,因为方法对于那些不好好关服务器的人不友好(指不用 Ctrl+C 或者 /stop 的)
因为那样无法触发 onDisable 所以无法触发还原事件
所以挂在 onEnable 上是最优解
调用 copyWorld 个人认为应该保持同步(Bukkit 不就是同步卸载插件的吗,所以应该不用担心 race condition)
对于存档过于大的服务器可能需要数十秒才能完全 备份/还原 完成(所以要考验你的硬盘 IO 啦!)
当然,以上只是最坏的解决方案,但是也最有效
END
轮子我已经造好了,在遵循君子协议的情况下随便 skid
https://github.com/NyaStudio/MapBackup