供求信息
供求信息文章用户商铺帮助新闻社区导航问答

{{userData.name}}已认证

文章

评论

关注

粉丝

¥{{role.user_data.money}}
{{role.user_data.credit}}
您已完成今天任务的
  • 私信列表所有往来私信

  • 财富管理余额、积分管理

  • 推广中心推广有奖励

    NEW
  • 任务中心每日任务

    NEW
  • 成为会员购买付费会员

  • 认证服务申请认证

    NEW
  • 小黑屋关进小黑屋的人

    NEW
  • 我的订单查看我的订单

  • 我的设置编辑个人资料

  • 进入后台管理

  • 首页
  • 商铺
  • 友链
  • 社区
  • 帮助
  • 关于
    • 隐私政策
    • 儿童个人信息保护政策及监护人须知
    • 用户注册协议
  • APP
投稿

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

  • 默认
  • 24年10月21日
  • 编辑
RTAkland
内容目录

起因

我在开发机器人的时候想到了一个在群内可以进行压测别人的服务器(很缺德, 慎用), 所以就研究了几个小时的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.本站大部分内容均收集于网络!若内容若侵犯到您的权益,请发送邮件至:[email protected],工作室将第一时间处理!

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

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

默认

更好地使用Notion

2024-9-25 21:17:23

学习默认

深澜自动登录脚本思路

2024-12-21 13:16:00

1 条回复 A文章作者 M管理员

您必须登录或注册以后才能发表评论

  1. 小满1221
    小满1221M站长 永久会员超能力者lv5
    24年10月22日

    乐

关于作者

RTAkland

超能力者lv5

文章

17

评论

8

关注

2

粉丝

3
[文章] 记录一下Kotlin CInterop的使用
[文章] 记录一下在Kotlin Native上嵌入资源的插件开发过程
[文章] 记录一下使用Compose multiplatform for web的使用
[文章] Kotlin基础教程
Ta的全部动态

文章聚合

  • TOP1
    [已知会封号,谨慎]AlwaysData加速Alist访问GoogleDrive和PikPak

    [已知会封号,谨慎]AlwaysData加速Alist访问GoogleDrive和PikPak

    22年5月17日
  • TOP2
    EDU教育邮箱(本站有售)激活可续期,数据可迁移的Notion个人Pro版(访客数量和上传文件大小无限制,30天文件历史记录)

    EDU教育邮箱(本站有售)激活可续期,数据可迁移的Notion个人Pro版(访客数量和上传文件大小无限制,30天文件历史记录)

    22年5月29日
  • TOP3
    无验证的100mb 免费全能空间alwaysdata的注册与配置免费二级域名SSL

    无验证的100mb 免费全能空间alwaysdata的注册与配置免费二级域名SSL

    22年5月29日
  • 华为Ensp安装包及安装教程

    华为Ensp安装包及安装教程

    22年10月17日
  • 免费主机和部署Alist

    免费主机和部署Alist

    23年2月11日
  • 如何优雅地白嫖微软的E5开发者

    如何优雅地白嫖微软的E5开发者

    22年7月9日
❯
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索
客服
  • 扫码打开当前页

  • 官网QQ群

返回顶部
幸运之星正在降临...
点击领取今天的签到奖励!
恭喜!您今天获得了{{mission.data.mission.credit}}积分

今日签到

连续签到

  • {{item.credit}}
  • 连续{{item.count}}天
查看所有
我的优惠劵
  • ¥优惠劵
    使用时效:无法使用
    使用时效:

    之前

    使用时效:永久有效
    优惠劵ID:
    ×
    限制以下商品使用: 限制以下商品分类使用: 不限制使用:
    [{{ct.name}}]
    所有商品和商品类型均可使用
没有优惠劵可用!

购物车
  • ×
    删除
购物车空空如也!

清空购物车 前往结算
您有新的私信
没有新私信
写新私信 查看全部
Copyright © 2025 云电工作室
・蒙ICP备2022000574号 ・蒙公网安备 15042902150512号
查询 10 次,耗时 0.3326 秒
首页店铺
我的客服