MC的生物发光逻辑以及方块发光

前言

如何在只使用Bukkit服务端插件的情况下,不改动客户端逻辑使MC的生物和方块发出不同颜色的光?

  • 游戏版本:1.12.2

  • 环境:Taboolib 6.2,Kotlin 1.9.24

  • 注:以下关于数据包的描述均局限于1.12.2的通讯协议(340),命名采用Bukkit NMS的命名,其他版本有出入

MC的发光逻辑

MC生物(注意是生物)的发光逻辑为:

当服务端DataWatcher(用于在服务端和客户端之间同步实体数据)中生物的标志位Flags字段的第六位(0x40)为1时,则实体发光,当为0时实体不发光,同步DataWatcher数据的通过发送PacketPlayOutEntityMetadata数据包实现

MC生物发彩色光(“发光”只是发白光,我们谈论的是发出不同颜色的光)的逻辑取决于MC的团队机制,处在同一团队的成员会发出某种颜色的光,它的逻辑为:

服务端判断团队并发送PacketPlayOutScoreboardTeam数据包实现

因此,为了实现我们的目的,我们需要伪造这两个数据包并发送给客户端

发光的实现

我们的发光标志位为

kotlin 复制代码
const val GLOWING_FLAG: Byte = (1 shl 6).toByte()

之后我们需要得到生物标志位Flags,它在Entity.class 中,混淆名在1.12.2中为“Z”,可以通过反射得到

kotlin 复制代码
private val nmsEntityClass by unsafeLazy {
    ReflexClass.of(net.minecraft.server.v1_12_R1.Entity::class.java)
}

@Suppress("UNCHECKED_CAST")
val DATA_SHARED_FLAGS_ID by unsafeLazy {
    nmsEntityClass.getLocalField("Z").get() as DataWatcherObject<Byte>
}

之后我们便得到了可以操控Flags的方法

kotlin 复制代码
private fun setMetaData(receiver: Player, entityID: Int, flags: List<Byte>) {
    val dataWatcherItem = flags.map { DataWatcher.Item(DATA_SHARED_FLAGS_ID, it) }
    receiver.sendEntityMetadataPacket(entityID, dataWatcherItem)
}

最后还需要发送假的PacketPlayOutEntityMetadata 数据包给客户端

kotlin 复制代码
fun Player.sendEntityMetadataPacket(entityID: Int, dataItems: List<DataWatcher.Item<*>>) {
    val packetPlayOutEntityMetadata = PacketPlayOutEntityMetadata()
    packetPlayOutEntityMetadata.setLocalProperty("a", entityID)
    packetPlayOutEntityMetadata.setLocalProperty("b", dataItems)

    (this as CraftPlayer).handle.playerConnection.sendPacket(packetPlayOutEntityMetadata)
}

其中混淆字段

  • a: entityID
  • b: 包装的DataWatcher字段类型

之后发送GLOWING_FLAG 即可

发彩色光的逻辑
在将生物设置为发光后,要使其发彩色光,需要操作生物的队伍

kotlin 复制代码
    fun Player.sendColorBasedTeamCreatePacket(color: ChatColor) {
        val packetPlayOutScoreboardTeam = PacketPlayOutScoreboardTeam()
        packetPlayOutScoreboardTeam.setLocalProperty("a", "glow-${color.char}")
        packetPlayOutScoreboardTeam.setLocalProperty("c", color.toString())
        packetPlayOutScoreboardTeam.setLocalProperty("e", EnumNameTagVisibility.ALWAYS.e)
        packetPlayOutScoreboardTeam.setLocalProperty("f", EnumTeamPush.NEVER.e)
        packetPlayOutScoreboardTeam.setLocalProperty("g", color.ordinal)
        packetPlayOutScoreboardTeam.setLocalProperty("h", emptyList<String>())
        packetPlayOutScoreboardTeam.setLocalProperty("i", 0)

        (this as CraftPlayer).handle.playerConnection.sendPacket(packetPlayOutScoreboardTeam)
    }

    fun Player.sendColorBasedTeamDestroyPacket(color: ChatColor) {
        val packetPlayOutScoreboardTeam = PacketPlayOutScoreboardTeam()
        packetPlayOutScoreboardTeam.setLocalProperty("a", "glow-${color.char}")
        packetPlayOutScoreboardTeam.setLocalProperty("i", 1)

        (this as CraftPlayer).handle.playerConnection.sendPacket(packetPlayOutScoreboardTeam)
    }

    fun Player.sendColorBasedTeamEntityAddPacket(teamID: String, color: ChatColor) {
        val packetPlayOutScoreboardTeam = PacketPlayOutScoreboardTeam()
        packetPlayOutScoreboardTeam.setLocalProperty("a", "glow-${color.char}")
        packetPlayOutScoreboardTeam.setLocalProperty("h", listOf(teamID))
        packetPlayOutScoreboardTeam.setLocalProperty("i", 3)

        (this as CraftPlayer).handle.playerConnection.sendPacket(packetPlayOutScoreboardTeam)
    }

    fun Player.sendColorBasedTeamEntityRemovePacket(teamID: String, color: ChatColor) {
        val packetPlayOutScoreboardTeam = PacketPlayOutScoreboardTeam()
        packetPlayOutScoreboardTeam.setLocalProperty("a", "glow-${color.char}")
        packetPlayOutScoreboardTeam.setLocalProperty("h", listOf(teamID))
        packetPlayOutScoreboardTeam.setLocalProperty("i", 4)

        (this as CraftPlayer).handle.playerConnection.sendPacket(packetPlayOutScoreboardTeam)
    }

即发送伪造的PacketPlayOutScoreboardTeam 数据包,它的混淆字段为

  • a:队伍名称
  • b:队伍后缀
  • c:队伍前缀
  • d,e,f:涉及到队伍成员的名字,碰撞箱设置等
  • g:队伍颜色
  • h:队伍成员
  • i:操作数 0:创建队伍/1:删除队伍/3:加入队伍成员/4:踢出队伍成员

!!但注意:在NMS1.12.2中,"g"这个字段是没有用的,队伍的颜色取决于“c”(Mojang的史山发力了)

发送这几个数据包即可让生物发出不同颜色的光

方块发光?

我们知道方块并不是生物,那我们如何让方块的边缘发光呢?

我们知道,MC中的方块大多是正方形(台阶等除外),所以我们只需要生成一个隐形的,和方块位置重叠的,大小正好合适的生物,让后让其发光,就可以模拟方块发光的效果,而“潜影贝”这个生物正好符合我们的需求

kotlin 复制代码
fun Player.sendCreateDummyEntityShulkerOn(location: Location): Pair<Int, String> {
    val nmsShulker = EntityShulker((this.world as CraftWorld).handle)
    nmsShulker.setLocation(location.x, location.y, location.z, location.yaw, location.pitch)
    nmsShulker.isNoGravity = true
    nmsShulker.isSilent = true
    nmsShulker.isNoAI = true
    nmsShulker.isInvisible = true
    nmsShulker.collides = false
    val uuid = nmsShulker.uniqueID
    val entityID = nmsShulker.id

    val packetPlayOutSpawnEntityLiving = PacketPlayOutSpawnEntityLiving(nmsShulker)
    (this as CraftPlayer).handle.playerConnection.sendPacket(packetPlayOutSpawnEntityLiving)

    return Pair(entityID, uuid.toString())
}

fun Player.sendRemoveDummyEntityShulker(entityID: Int) {
    val packetPlayOutEntityDestroy = PacketPlayOutEntityDestroy(entityID)

    (this as CraftPlayer).handle.playerConnection.sendPacket(packetPlayOutEntityDestroy)
}

通过以上代码我们可以伪造一个PacketPlayOutSpawnEntityLiving 数据包,它负责告诉客户端在指定位置活实体的生成,我们生成一个伪造的潜影贝,然后使用它的EntityID和前两节的方法,即可模拟方块的发光,若想移除发光,发送PacketPlayOutEntityDestroy 数据包移除潜影贝即可

新的方块发光方法:FallingBlock

在1.18以下的版本,NMS服务端存在一个漏洞,即掉落方块实体的材质可以为非原版掉落方块的材质,利用此性质,我们可以将客户端的方块更新为掉落方块生物以后再对这个生物执行发光,它的优缺点非常明显
优点

  • 发光可以完美贴合方块的边框,使用潜影贝只能为正方形

缺点

  • 在更高版本Mojang修复了这个漏洞,客户端不会处理非原版掉落方块实体(如沙子)以外的其他材质
  • 由于方块被更新为了生物,客户端的方块交互会被完全破坏,碰撞箱也会变的很诡异,因此只可用于展示

此部分涉及到的代码不再叙述,感兴趣的可以在下方找到项目代码

代码

项目代码目前被Arim合并,查看Arim

游客

全部评论 (0)

暂无评论,快来抢沙发吧~