使用Kotlin和MCProtocolLib写一个MC假人压测程序

AI智能摘要
本文介绍了一个使用Kotlin语言和MCProtocolLib库编写的Minecraft服务器假人压测程序。该程序可以模拟多个机器人客户端连接至指定服务器,执行自动攻击周围实体、发送聊天消息等基本操作,从而对服务器进行压力测试。文章提供了核心代码片段,展示了如何建立连接、处理服务器数据包以及通过指令控制机器人集群的启动、停止和发送消息。
— AI 生成的文章内容摘要

起因

我在开发机器人的时候想到了一个在群内可以进行压测别人的服务器(很缺德, 慎用), 所以就研究了几个小时的MCProtocolLib 再加上看别人的代码终于会用这个库了, 本教程基于Kotlin(2.0.20)

依赖

[versions]
kotlinVersion = "2.0.20"
mcprotolobVersion = "1.21-SNAPSHOT"

[libraries]
mcprotocollib = { group = "org.geysermc.mcprotocollib", name = "protocol", version.ref = "mcprotolobVersion" }
[bundles]

[plugins]
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlinVersion" }
dependencies {
    implementation(libs.mcprotocollib)
}

使用了Gradle的Version Catalog来集中管理依赖

代码

仅给出核心代码

class MCClient(
    private val serverHost: String,
    private val serverPort: Int,
    private val botName: String
) {
    private lateinit var client: TcpClientSession
    private val logger = Logger.getLogger<MCClient>()
    private val timer = Timer()
    private val entitiesId = mutableMapOf<Int, Triple<Double, Double, Double>>()

    fun createClient(): TcpClientSession {
        val protocol = MinecraftProtocol(botName)
        client = TcpClientSession(serverHost, serverPort, protocol)
        return client
    }

    fun runBot(): MCClient {
        client.addListener(object : SessionAdapter() {
            override fun packetReceived(session: Session, packet: Packet) {
                when (packet) {
                    // 进入服务器需要资源包的话自动同意
                    is ClientboundResourcePackPushPacket -> {
                        val loadedPacket =
                            ServerboundResourcePackPacket(packet.id, ResourcePackStatus.SUCCESSFULLY_LOADED)
                        session.send(loadedPacket)
                    }
                    // 玩家被tp然后接受tp
                    is ClientboundPlayerPositionPacket -> {
                        session.send(ServerboundAcceptTeleportationPacket(packet.teleportId));
                    }
                    // 系统信息
                    is ClientboundSystemChatPacket -> {
                        logger.trace("系统 >>> {}", packet.content)
                    }
                    // 玩家聊天信息logger.trace
                    is ClientboundPlayerChatPacket -> {
                        logger.trace("{} >>> {}", packet.name, packet.content)
                    }
                    // 死亡自动复活
                    is ClientboundPlayerCombatKillPacket -> {
                        logger.info("Bot: $botName 死亡, 已自动复活")
                        session.send(ServerboundClientCommandPacket(ClientCommand.RESPAWN))
                    }
                    // 每次被攻击就随机转头(很鸡肋的功能)
                    is ClientboundDamageEventPacket -> {
                        session.send(
                            ServerboundMovePlayerRotPacket(
                                true,
                                Random.nextInt(-90, 90).toFloat(),
                                Random.nextInt(-90, 90).toFloat()
                            )
                        )
                    }
                    // 每次接收到实体移动的数据包就将这个实体的id和对应的坐标放入一个map
                    is ClientboundMoveEntityPosPacket -> {
                        val entityId = packet.entityId
                        val x = packet.moveX
                        val y = packet.moveY
                        val z = packet.moveZ
                        entitiesId[entityId] = Triple(x, y, z)
                    }
                }
            }
        })
        client.connect()
        // 每隔1秒攻击一次周围的实体
        timer.scheduleAtFixedRate(object : TimerTask() {
            override fun run() {
                if (client.isConnected) {
                    entitiesId.forEach {
                        val x = it.value.first
                        val y = it.value.second
                        val z = it.value.third
                        val entityId = it.key
                        if (isWithinRange(x, y, z)) {
                            attackEntity(entityId)
                        } else {
                            entitiesId.remove(entityId)
                        }
                    }
                }
            }
        }, 1000L, 1000L)
        return this
    }

    // 判断一个坐标是否在自己可以出碰到的范围内
    private fun isWithinRange(x: Double, y: Double, z: Double): Boolean {
        val playerX = 0.0
        val playerY = 0.0
        val playerZ = 0.0
        val distance = sqrt((x - playerX).pow(2) + (y - playerY).pow(2) + (z - playerZ).pow(2))
        return distance <= 5
    }

    // Killaura 外挂功能属于是难崩
    private fun attackEntity(entityId: Int) {
        val attackPacket =
            ServerboundInteractPacket(entityId, InteractAction.ATTACK, Hand.entries.random(), Random.nextBoolean())
        client.send(attackPacket)
    }

    private fun sendMessage(content: String) =
        client.send(ServerboundChatPacket(content, Instant.now().toEpochMilli(), 0L, null, 0, BitSet()))

    private fun executeCommand(command: String) = client.send(ServerboundChatCommandPacket(command.substring(1)))

    fun sendChat(text: String) {
        if (text.startsWith("/")) {
            this.executeCommand(text)
        } else {
            this.sendMessage(text)
        }
    }

    fun disconnect() = client.disconnect("Bye~")
}

搭配ROneBot框架实现的指令部分


@CommandDescription("Java版服务器假人压测")
class MCBotCommand : BaseCommand() {
override val commandNames = listOf("/mcbot")
private val userClientMap = mutableMapOf<Long, MutableList<MCClient>>()

private fun getPlayerList(host: String, port: Int): Pair<Int, Int> {
    val response = JavaPing().ping(host, port, 8000)!!
    return response.players.max to response.players.online
}

override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
    val clients = mutableListOf<MCClient>()
    val action = args.first()
    when (action) {
        "start" -> {
            if (userClientMap.containsKey(message.sender.userId)) {
                message.reply("你有一个正在进行的任务不能开启另外一个任务请先使用`/mcbot cancel`取消任务")
                return
            }
            val address = args[1]
            val host = address.split(":").first()
            val port = address.split(":").last().toInt()
            val count = args[2].toInt()
            val executor = Executors.newFixedThreadPool(count)
            val (maxPlayer, onlinePlayer) = this.getPlayerList(host, port)
            val availableSit = maxPlayer - onlinePlayer
            if (count > availableSit) {
                message.replyAsync("当前服务器仅剩余${availableSit}个玩家可进入!!")
            }
            (1..count).forEach {
                val client = MCClient(host, port, generateRandomString())
                    .also { it.createClient() }
                executor.execute { clients.add(client.runBot()) }
            }
            userClientMap[message.sender.userId] = clients
            message.reply("成功开启任务: $address, $count")
        }

        "cancel" -> {
            if (userClientMap.containsKey(message.sender.userId)) {
                userClientMap.entries.forEach {
                    if (it.key == message.sender.userId) {
                        it.value.forEach { it.disconnect() }
                        userClientMap.remove(it.key)
                    }
                }
                message.reply("成功取消任务")
            } else {
                message.reply("你还没开启任务呢")
            }
        }

        "message" -> {
            val content = args.drop(1).joinToString(" ").trim()
            userClientMap[message.sender.userId]!!.forEach { it.sendChat(content) }
        }
    }
}

}



> 你可以点击[这里](https://github.com/RTAkland/FancyBot/blob/492f36d6a53a59d6dc4d6a821ba8e681771751b3/src/main/kotlin/cn/rtast/fancybot/commands/misc/MinecraftCommand.kt#L600-L659)来查看源代码
温馨提示:

1.本站大部分内容均收集于网络!若内容若侵犯到您的权益,请发送邮件至:xiaoman1221@yhdzz.cn,工作室将第一时间处理!

2.资源所需价格并非资源售卖价格,是收集、整理、编辑详情以及本站运营的适当补贴,并且本站不提供任何免费技术支持。

3.所有资源仅限于参考和学习,版权归原作者所有。

Kotlin学习技术默认

使用ROneBot框架开发一个机器人,

2024-9-7 13:28:50

Kotlin技术

记录一下Fabric高版本模组开发的注意事项

2025-1-1 16:24:53

30 条回复 A文章作者 M管理员
  1. 潇洒如风

    感觉可以优化下实体距离判断,每次都遍历Map会不会有点耗性能?

  2. Version Catalog管理依赖确实方便,尤其是多模块项目。

  3. 花园的蜜蜂

    自动复活那个处理挺好的,我之前写的假人死了就卡那了。

  4. 月映古井

    看起来不难,周末有空照着写一个玩玩。

  5. 梦回星砂

    想试试压测朋友的服务器,但又怕被骂,纠结。

  6. 天狼星

    代码思路挺清晰的,就是随机转头那个功能太搞笑了,真有人用吗hhh

  7. ToxicSniper

    Timer做定时任务确实有点老了,可以考虑换成协程的delay?

  8. 影界旅者

    要是能加个自动挖矿或者种地功能就好了,纯压测有点无聊。

  9. 柴犬阳

    这个库支持1.21.4吗,我用的是1.20.5不知道能不能兼容。

  10. 逆流

    前几天自己整了个类似的东西,也是卡在资源包同意那块好久

  11. 星尘

    只给核心代码确实有点懵,能不能放个完整工程参考下?

  12. 幸福的微笑

    MCProtocolLib对1.21支持一直不太稳定,你这个snapshot能打mod吗?

  13. 古刻心

    新人求助,这个要怎么部署到Linux后台跑啊?

  14. Hex_六角

    Kotlin写起来就是比Java舒服,尤其是这种异步处理。

  15. 虚无的拥抱

    感觉可以加个自动走路或者挖矿的功能?

  16. 笛仙高

    只给核心代码有点难受,完整项目有开源地址吗?

  17. 计时器那块用Timer,会不会有内存泄漏的风险?

    • 定时任务用Timer确实得小心,换成ScheduledExecutorService更稳吧

    • 自动复活+随机转头是真有节目效果hhh

  18. 鳄鱼凶凶

    能压测群友服务器可太缺德了,建议慎用啊233

    • 精神小伙

      这玩意搞不好真能把服务器干崩啊,刺激👍

    • 龙鳞骑士

      之前自己试过写假人,握手阶段就各种超时,楼主这连接处理挺稳的

  19. 孔雀羽

    之前想写个类似的东西,被连接和发包那块卡了好久,楼主思路清晰。

  20. Aurora’s Kiss

    新人问下,这个库支持1.21.4吗?

  21. WiFi信号守护者

    依赖管理用Version Catalog好评,比直接写在build.gradle里清爽多了。

  22. 金牛座的守护

    攻击实体那段随机转头有点搞笑啊hhh

  23. 光明咏叹

    这代码看着挺简洁的,回头试试看。

  24. 小满1221

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索