起因
我在开发机器人的时候想到了一个在群内可以进行压测别人的服务器(很缺德, 慎用), 所以就研究了几个小时的
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.所有资源仅限于参考和学习,版权归原作者所有。
乐