【全员逃走中】幕后: 移动警报那些事
观前提醒
本文章有剧透内容。虽然标题可能已经足够剧透...但仍然推荐您先看完原片再看本文。 原片 ->【全员逃走中】布莱克城 第三季https://b23.tv/UgVxO2F 本期专栏是我所做的移动警报插件的技术原理解析。阅读前请确定您有一定的理解能力。
看个乐也行
插件的源代码可以在 GitHub 的 SNWCreations/WarnOnMove 仓库找到。 插件基于 Bukkit API 1.16.5 。 运行时环境: Arclight 1.16.5, Java 8 这个插件并没有太多的细节可讲,主要是围绕 org.bukkit.event.player.PlayerMoveEvent 进行的事件监听与判断,以及一些情景的特殊处理。
下文使用 player 一词表示事件中的玩家。
基础原型
插件的判断条件是 玩家的头盔栏是否放置着一个石质按钮。 源代码中采用了先保存物品引用再检查的方法。 其实也可以使用 java.util.Optional 类简化。 代码如下: Optional.ofNullable(player.getInventory().getHelmet()).map(i -> i.getType() == Material.STONE_BUTTON).orElse(false) 在满足条件后,我们即可获取玩家所在的位置及其所在世界,然后调用 World.playSound 方法将播放声音的要求下发给客户端,从而完成警报效果。 这是最简单的原型。 特殊情况
后续测试时发现,仅移动视角也会发出声音。 所以我借鉴了 org.bukkit.Location 中 equals 方法的内容,比较 PlayerMoveEvent 中 from 与 to 的 x, y, z 坐标是否一致,若一致,认为是转动视角,无需播放声音,此时直接 return 以退出事件处理。 但声音频率过大,导致 "动多了就会聋掉" (茂茂原话,意思应该是 "移动一点点声音就会很大")。 对于这个问题,我认为是因为 PlayerMoveEvent 的产生只需要极短的距离,而导致短时间内调用次数过多,太多声音叠加导致的。 因此引入一个 Collection ,若 player 的 UUID 存在于这个 Collection ,则阻止发出声音。若不包括 player 的 UUID ,则在 playSound 后将其 UUID 添加到 Collection ,并预定一个在 1 秒后执行的计划任务,以将玩家的 UUID 从 Collection 中移除,使插件判定可以继续工作。 解除警报的实现?
这已经不是这个插件的范围了,它是用原版的命令实现的。
虽然这部分命令也是主要由我编写。
其实并不复杂。 首先我们规定一个标签(Minecraft 中的 /tag 命令的功能),这个标签只有那些有警报的玩家才会有,我们认为,有这个标签的玩家,头上一定有一个按钮,下文称这个标签为 warn 。 当一个玩家按下解除按钮时,背后有 4 条命令会被执行。它们的工作逻辑如下: 从有 warn 标签的玩家中随机选取一个添加上名为 r 的临时标签---清除所有持有 r 标签的玩家的头盔栏---清除所有持有 r 标签的玩家的 warn 标签---清除所有持有 r 标签的玩家的 r 标签。 翻译为 Minecraft 命令如下: /tag @r[tag=warn] add r /replaceitem entity @a[tag=r] armor.head air /tag @a[tag=r] remove warn /tag @a[tag=r] remove r (replaceitem 那条我不确定有没有写对,比较这个命令我太久没碰过了(捂脸)) 为什么这么设计?为什么不直接 @r[tag=warn]?
这么设计是为了避免选取到一个已经解除警报的玩家,以避免这次警报解除操作是无效的这种情况。 这就是移动警报的整个工作流程与实现原理。 喜欢的话点个关注吧!或许后续我还会发点幕后内容?owo