Skip to content

Latest commit

 

History

History
307 lines (198 loc) · 10.5 KB

3-3.md

File metadata and controls

307 lines (198 loc) · 10.5 KB

3-3 深入研究玩家

玩家是 Minecraft 的主体,没有玩家还谈什么游戏!

这一节我们专门研究玩家,这个类是 Player 接口。

与玩家有关的事件

之前我们说过,PlayerXXXEvent 是属于玩家的事件,可以通过 getPlayer 获取涉及到的玩家。

UUID

打开 Player 类的 JavaDocs(或者反编译查看源代码),你能看到许多方法。

UUID 是区别不同玩家所使用的标识符,正版账号的 UUID 由 Mojang 提供,盗版账号的 UUID 则由 OfflinePlayer: + 玩家的名字并进行哈希计算得出,当然这项工作已经由 Bukkit 为我们完成了。

getUniqueId 获得一个实体的 UUID,因为不仅玩家有 UUID,Minecraft 中的羊、末影龙、火焰弹也是有 UUID 的。

UUID id = Bukkit.getPlayer("ThatRarityEG").getUniqueId();
// 无论在哪个服务端上,上面这个 id 都是相同的

正版玩家的 UUID 是跟随账号的,不会因改名而改变。不过,如果名字相同,盗版玩家的 UUID 也不会改变。因此,我们建议使用 UUID 查找玩家。

Bukkit 提供了通过 UUID 查找玩家的方法重载,签名如下:

@Nullable
public static Player getPlayer(@NotNull UUID id)

另外还可以通过这个方法计算玩家的盗版 UUID:

@Nullable
public static UUID getPlayerUniqueId(@NotNull String playerName)

这个方法不需要玩家在线也能运转,它这样工作:

  • 如果服务器在离线模式下,则计算出这个名字对应的盗版 UUID
  • 如果服务器在正版模式下,并且玩家没有注册正版账号,则返回 null
  • 如果服务器在正版模式下,并且玩家注册了正版账号……???

最后一种情况的行为是不确定的,得到的结果不可靠,因此,对于正版玩家,应当等玩家在线时再使用 getUniqueId 来获取 UUID。

踢出玩家

kickPlayer 方法用于将玩家踢出服务器。

Bukkit.getPlayer("RarityEG").kickPlayer("信号不好");

就可以踢出名为 RarityEG 的玩家,踢出玩家时需要提供原因,所以这里可以填入诸如 java.lang.NullPointerException 或者「随机轰炸」之类的内容来迷惑玩家。(说真的——不要这样做!)

给予物品

要给玩家物品,一般使用 getInventory 获得物品栏再进行 addItem。这里一样是提供 ItemStack,它们的签名:

@NotNull
PlayerInventory getInventory()
@NotNull
HashMap<Integer, ItemStack> addItem(@NotNull ItemStack... items) throws IllegalArgumentException

addItem 返回的 HashMap 中包含了新物品栏中槽位与物品的对应关系,不过大多数情况下我们不使用这个结果。

ItemStack... 表示「可以传入任意个 ItemStack 类型的参数」,一个可以,两个也可以,几十上百个呢?那可能就像它说的一样,会触发 IllegalArgumentException 异常。

这里的 ItemStack 和上一节中的一样,可以进行各种设置后再交给玩家。

要给玩家经验,直接使用 Player 接口的 giveExp 即可:

default void giveExp(int amount)

它还有一个变种:

void giveExp(int amount, boolean applyMending)

如果将 applyMending 设为 false,那么这次添加的经验将不会用于「经验修补」附魔的修复,而直接加入了玩家经验条。

如果要单独进行修复呢?

int applyMending(int amount)

返回值是剩余的经验值。至于这些到底有没有加入到玩家的经验条中,要看各个版本的实现。

另外,如果要直接给玩家等级(在附魔时比较常用):

void giveExpLevels(int amount)

你可能会想知道玩家现在的经验等级:

int getLevel()

还有一个不怎么常用的 getExp 方法:

float getExp()

这个方法返回「到下一个等级的进度」,也就是说,如果你刚升到 26 级,那么这个值就接近 0,如果你马上就要 27 级了,那么这个值就接近 1。

如果要计算玩家的具体经验值,就需要先用 getLevel 获得等级,再通过 getExp 的结果获得经验进度,然后用 这张表 进行计算。这个相对而言比较困难,而且通常没有很大的意义(万一哪天 Mojang 改了经验值计算方式你就要重新来)。大多数情况下,有等级就足够了。

展示内容

展示给玩家的内容有几种:物品栏 GUI、书本视图、计分板、BOSS 血条等。

物品栏 GUI 我们已经说过,这里我们介绍书本视图。

要给玩家展示一本书,可以使用 openBook 方法:

void openBook(@NotNull ItemStack book)

这里只能使用 Material.WRITTEN_BOOK不能使用 Material.WRITING_BOOK

在调用 openBook 前:

  • 先创建一个 ItemStack,转换为 BookMeta
  • 确认 ItemStack 已经设置过了标题作者内容,三项缺一不可
  • 玩家处在一个可以接受信息的状态,玩家如果正在激烈地战斗,把书显示出来显然不合适

操作玩家数据

玩家的生命值、饱食度、生命上限等都可设置:

Player hacker = Bukkit.getPlayer("Hacker");
hacker.setHealth((double) 1); // 生命值
hacker.setFoodLevel(1); // 饱食度
hacker.setMaxHealth((double) 1); // 生命上限

等等。

需要注意的是有些方法(例如 setHealth)并没有写在 Player 接口中,而是写在了更通用的 Damageable 接口中,尝试使用 JavaDocs 中的搜索来找出它们吧!

移动玩家

要移动玩家,可以使用 teleport 方法,演示如下:

player.teleport(new Location(player.getWorld(), 10000, 64, 10000)); // x,y,z

Location 是玩家的新位置,它包含四个信息:

  • X
  • Y(高度)
  • Z
  • 玩家所属的世界

一般我们都是在当前世界中移动,因此可以通过 player.getWorld 方法获取玩家当前所属的世界。

设置资源包

setResourcePack 用于建议玩家使用一个资源包,这里只是建议,玩家是否接受需要在客户端决定。

void setResourcePack(String url, String hash)

这里的 hash 是资源包的 SHA-1 校验码,通常应该由资源包的制作者向你提供。(当然也可以自己算)

如果想知道玩家到底有没有接受资源包,可以调用这个方法:

@Nullable
PlayerResourcePackStatusEvent.Status getResourcePackStatus()

返回值是 PlayerResourcePackStatusEvent.Status 枚举中的一个:

  • ACCEPTED,下载已经开始
  • DECLINED,玩家拒绝
  • FAILED_DOWNLOAD,下载失败
  • SUCCESSFULLY_LOADED,已经加载

所以如果万不得已,需要强制设置玩家资源包时,可以监听 PlayerResourcePackStatusEvent,并等到 getResourcePackStatus 返回的结果是 PlayerResourcePackStatusEvent.Status.SUCCESSFULLY_LOADED 时再允许玩家做别的事。

!> 请尊重玩家
也许这样可以让玩家没法卸载资源包,但玩家有权退出服务器!亦请了解很多玩家的机器性能并不好,可能在加载资源包的过程中崩溃!此外,如果你的 CDN 分发速度不快……玩家没有那么好的耐心!请尊重玩家的选择

设置移动速度

这里的移动速度指的是走路速度,不影响玩家的跳跃。也就是说,即使你把速度设置为 0,玩家仍然能够通过跳跃来进行行进。

与此相对的还有一个设置飞行速度,可用于鞘翅的飞行或者创造模式。

两个方法的签名分别是:

void setWalkSpeed(float value) throws IllegalArgumentException
void setFlySpeed(float value) throws IllegalArgumentException
// 只能从 -1 到 1,否则会抛出异常

同样它们还有对应的 getWalkSpeedgetFlySpeed 用于检测玩家的当前速度。

另外这里还有几个方法可以检测玩家的移动状态:

boolean isSneaking()
// 是否在潜行
boolean isSprinting()
// 是否在冲刺
boolean isFlying()
// 是否在飞行
// 附赠三个 set 方法
void setSneaking(boolean sneak)
void setSprinting(boolean sprinting)
void setFlying(boolean flying)

另外还有一个不怎么常用的方法,可以允许玩家在生存模式飞行:

void setAllowFlight(boolean flight)

理论上这个可以突破配置文件中 allow-flight 的限制,但笔者没有经过确认。即使可以,很可能也会被某些反作弊插件拦下来,那对玩家就太冤枉了,所以不建议使用。

重设重生点

void setBedSpawnLocation(@Nullable Location location)

这就是重设重生点的方法。要注意的是,在 1.15 及以前的版本中,请不要将玩家的重生点设在下界或末地,否则可能会出现意料之外的后果

这个方法需要目标位置是一张床,如果不是,可以使用下面这个变种:

void setBedSpawnLocation(@Nullable Location location, boolean force)

第二个参数传入 true 即可强制重设,也不需要有床。

发送信息

这个我们已经做过了,里面可以使用 ChatColor 设置颜色样式。

void sendMessage(String message)

以及它的批量发送版本:

void sendMessage(String[] messages)
// 又见到数组了

还可以「假装」是某个实体发给该玩家的:

void sendMessage(UUID sender, String message)
// 批量版本
void sendMessage(UUID sender, String[] messages)

由于所有的实体(Entity)都有 getUniqueId,因此可用这个方法实现「我和末影龙聊天」这样的效果。

如果我想向玩家发送 § 这样的字符呢?

可以使用:

void sendRawMessage(@NotNull String message)

这会将 message 原封不动地发给玩家,不进行解析或者渲染。

控制玩家说话

可以使用 Player 接口的这个方法让玩家说话:

void chat(@NotNull String msg)

这当然也可以用来执行命令,但如果要执行命令,还有一个专门的:

void performCommand(@NotNull command)

通过这个方法执行命令不需要输入 /,笔者也不知道 Bukkit 为什么要设置两个方法,一般用 chat 就好的。


上面我们只是非常简略地介绍了有关 Player 接口的一些实用方法,限于篇幅无法全部介绍,你可以参考 有关 Player 接口的 JavaDocs