十三、资源包与玩家资料
1.资源包(Resource Pack)
早期的 Minecraft 并没有资源包一说,而是被叫做材质包。有些服务器为了让玩家拥有更好的游戏体验,一般会在自己特制的客户端中存放一些资源包供玩家加载。
显然,使用资源包的主动权在玩家手中,而且服务器无法检测到玩家是否开启了资源包(在某些场合下,资源包必须开启)。
服务器觉得非常没有面子,于是开始了复仇之路……
复仇个屁啊,server.properties
写一行require-resource-pack=true
,玩家要是不乖乖下载资源包直接断开连接(小声)
1.1.通通安排
如果你是一名插件制作者,且你的插件需要指定的资源包,必须提供资源包的uri,客户端将会通过该地址下载指定的资源包。
如果你是一名腐竹(插件教程还有腐竹看,ε=(´ο`*))) 唉),你可以打开server.properties
文件,找到resource-pack=
这一行,填上资源包的uri
,建议找到resource-pack-sha1=
这一行把它填上(是资源包的SHA-1
值),会提高资源包的可靠性和缓存的有效性。
额外的:
你可以调用Bukkit
类中的getResourcePack
方法来获取服务器资源包的uri
,也可以调用getResourcePackHash
获取资源包的SHA-1
值。
你也可以获取自定义提示下载资源包时的消息,调用getResourcePackPrompt
来实现此功能。
但是,你不可以在插件中更改以上获取到的值,只有通过更改server.properties
文件才是有效的。
1.2.获取主动
于是,在之后的版本中,服务器可以请求玩家下载并使用指定的资源包,但是是否下载并使用资源包的主动权依然在玩家手中,在开始下载之前会弹出窗口提示是否下载资源包,如果玩家选择“否”那么也就无能为力了。
Player player = (Player) sender;
player.setResourcePack("<uri>");
如上,填上资源包的uri
,这样客户端会弹出窗口请求下载。
1.3.你在干什么?
真是这样吗?
不!当玩家对弹出的窗口采取行动时会触发PlayerResourcePackStatusEvent
事件,插件可以获取资源包的状态,必定会是以下四种状态之一:
状态 | 解释 |
---|---|
ACCEPTED | 客户端接受了资源包, 并开始下载 |
DECLINED | 客户端拒绝接受资源包 |
FAILED_DOWNLOAD | 客户端接受了资源包, 但下载失败 |
SUCCESSFULLY_LOADED | 资源包成功地下载并应用到了客户端 |
如果服务器要求比较苛刻,可以通过这个事件获取资源包状态,要是被拒绝了直接踢出玩家(嘿嘿)。
至于如何监听事件,在上一章已经提到过,这里就不再赘述了。
附:关于插件包资源
插件包中的资源应该位于resource
这个文件夹当中,具体如图示:
可以调用插件主类中getResource
方法获取相应的资源:
@Nullable
@Override
public InputStream getResource(@NotNull String filename);
//获取插件相应资源
获取名为filename
的资源,返回类型为InputStream
。
也可以保存内置于插件的.jar
文件的某个资源的原始内容。
@Override
public void saveResource(@NotNull String resourcePath, boolean replace);
当参数replace
为true
时表示覆盖内置资源的所有内容。第一个参数表示插件.jar
文件中查找的内置资源路径。
更多关于资源包的方法或其他请参阅 API文档。
2.玩家资料(PlayerProfile)
注:玩家资料在较新版本被引入,旧版本(已知是1.17及以前)没有PlayerProfile
玩家资料,顾名思义,关于某一位玩家的相关资料。想象一下,一摞资料文件放在你的桌前,你尽情翻阅着每位玩家的相关信息,是不是有一种________的感觉。
一份玩家资料(PlayerProfile
接口)至少包含玩家唯一ID或非空名字,完整的玩家资料除两者外还要有玩家的纹理。
PlayerProfile
是一个接口,所以你可以放心地使用。
2.1.「打印文件」
我们可以创建一份新的资料,至于是哪个玩家的资料取决于玩家名:
@NotNull
PlayerProfile createPlayerProfile(@Nullable UUID uniqueId, @Nullable String name);
createPlayerProfile
方法在Server
接口中,可以使用getServer
方法来获取:
Bukkit.getServer().createPlayerProfile(UUID.randomUUID(), "<玩家名>");
返回类型是PlayerProfile
接口,这就是一份玩家资料。
2.2.「审阅文件」&「核验文件」
我们拿到了一份玩家资料,首先要检查资料是否齐全,即是否是一份完整的玩家资料,我们可以使用isComplete
方法来检查资料是否完整。如果不完整,我们还可以调用update
方法来更新玩家资料:
@NotNull
CompletableFuture<PlayerProfile> update();
必须注意,返回的类型是CompletableFuture
类。因为玩家的相关资料都在官方网站中,所以update
方法需要在另外一个线程中发起一个对外连接, 以拉取官方最新资料属性。
可以使用如下代码来操作更新完成的玩家资料:
profile.update().thenAcceptAsync(updatedProfile -> {
//这里操作更新完成的玩家资料
//...
}, runnable -> Bukkit.getScheduler().runTask(this, runnable));
thenAcceptAsync
是指异步对单个处理完的结果进行操作(消费),但它不和父线程使用同一个线程,而是使用自定义的线程池,而在最后一个参数为该方法提供了Executor
框架。
关于CompletableFuture<T>
,有兴趣可以自行了解,本章不做详细讲解。
玩家的唯一ID及玩家名对我们来说没有一丁点价值,有价值的是玩家的纹理,纹理包括玩家的皮肤(Skin)和披风(Cape),我们可以用getSkin
和getCape
分别获取皮肤和披风,注意二者的返回类型均为URL
类。
可以用setSkin
和setCape
方法分别设置玩家的皮肤和披风为指定的URL
。但是必须指向 Minecraft 纹理服务器,比如下面的URL
就是一个栗子:
http://textures.minecraft.net/texture/b3fbd454b599df593f57101bfca34e67d292a8861213d2202bb575da7fd091ac
PlayerProfile profile = Bukkit.getServer().createPlayerProfile(UUID.randomUUID(), "<玩家名>");
PlayerTextures textures = profile.getTextures();
try {
textures.setSkin(new URL("http://textures.minecraft.net/texture/b3fbd454b599df593f57101bfca34e67d292a8861213d2202bb575da7fd091ac"));
} catch(Exception e) {
e.printStackTrace();
sender.sendMessage("发生异常:" + e.getMessage());
}
这样我们就设置了玩家的皮肤(其实本人没有试过行不行qwq)。
最后,为辨别真假,我们使用isSigned
方法来判断这份「文件」是否有效地签名,至此一份资料就审阅完了。
感觉这是自我发布教程以来为数不多的文章了啊,虽然是“简简单单”两个方面但是唠叨了四千多字,实感不容易,也希望读者能够消化消化。
附:Bukkit 和 Spigot 之间的关系
有很多人不明白 Bukkit 和 Spigot 之间有何关系:教程写的是 Bukkit,给的文档确是 Spigot API 文档,让很多人一头雾水。
如果你在翻阅文档时,大致浏览一下 文档 所列出的所有程序包的话,你会发现几乎所有程序包都是以org.
开头,只有三个程序包以net.md_5.bungee.
开头。这三个程序包在万众之上,所以非常显眼。
之前提过,Bukkit 是一款开源、免费的项目,但由于种种原因而极其短命。Spigot 项目则继承了 Bukkit,一直存活到现在。它的地位与 Forge 和 Fabric 不相上下。
某种意义上,你可以理解为 Bukkit = Spigot。除此之外还有以org.spigotmc.
开头的程序包,这是 Spigot 所特有的。
不难看出 Spigot 是一个大熔炉,用面向对象语言的特性形容 Spigot 就是两个字——继承!
上一篇:我的世界Bukkit服务器插件开发教程(十二)物品与监听事件
下一篇:我的世界Bukkit服务器插件开发教程(十四)消息和命令补全器