Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af4e915421 | ||
|
|
191373c430 | ||
|
|
2c2e413d72 | ||
|
|
9d8800a671 | ||
|
|
67354e0917 | ||
|
|
73c51871c9 | ||
|
|
5b520dc774 | ||
|
|
4475f6ce7a | ||
|
|
704af34048 | ||
|
|
f0a2ce1c56 |
No files matched your search
@@ -1,7 +1,7 @@
|
|||||||
# Minecraft bot stress tester
|
# Minecraft bot stress tester
|
||||||
🤖 A simple open source app written in Java used for stress testing Minecraft servers with bots (fake players).
|
🤖 A simple open source app written in Java used for stress testing Minecraft servers with bots (fake players).
|
||||||
💥 It can be also used to test plugins or minigames.
|
💥 It can be also used to test plugins or minigames.
|
||||||
✔️ The MC version of the bots is 1.19. (Does not support 1.19.1 yet)
|
✔️ The MC version of the bots is 1.19.1 (and 1.19.2)
|
||||||
|
|
||||||
## 🆒 Features
|
## 🆒 Features
|
||||||
- ✅ Connect as many bots as you want
|
- ✅ Connect as many bots as you want
|
||||||
@@ -18,7 +18,8 @@ Use of pre-compiled jar from [releases](https://github.com/crpmax/mc-bots/releas
|
|||||||
When running, you can write a chat message to the terminal to send it by all bots.
|
When running, you can write a chat message to the terminal to send it by all bots.
|
||||||
|
|
||||||
## 🧪 Example
|
## 🧪 Example
|
||||||
`java -jar mc-bots-1.2.0.jar -s 192.168.0.189:25565 -d 10 11 -r -p BOT_ -c 30`
|
`java -jar mc-bots-1.2.4.jar -s 192.168.0.189:25565 -p BOT_ -d 4000 5000 -c 30 -r`
|
||||||
|
This will connect 30 bots to server at 192.168.0.189:25565 with delay 4000-5000 ms and will use real-looking nicknames prefixed with BOT_
|
||||||
|
|
||||||
<img src="https://imgur.com/XWcckas.png" title="Connected bots" width="350px"/>
|
<img src="https://imgur.com/XWcckas.png" title="Connected bots" width="350px"/>
|
||||||
<img src="https://imgur.com/CvJq1Io.gif" title="Sending chat message by bots" width="350px"/>
|
<img src="https://imgur.com/CvJq1Io.gif" title="Sending chat message by bots" width="350px"/>
|
||||||
|
|||||||
+4
-3
@@ -3,7 +3,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group 'me.creepermaxcz.mc-bots'
|
group 'me.creepermaxcz.mc-bots'
|
||||||
version '1.2.4'
|
version '1.2.5'
|
||||||
|
|
||||||
sourceCompatibility = 1.8
|
sourceCompatibility = 1.8
|
||||||
targetCompatibility = 1.8
|
targetCompatibility = 1.8
|
||||||
@@ -11,11 +11,12 @@ targetCompatibility = 1.8
|
|||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { url 'https://jitpack.io' }
|
maven { url 'https://jitpack.io' }
|
||||||
maven { url 'https://repo.opencollab.dev/maven-releases' }
|
maven { url 'https://repo.opencollab.dev/maven-snapshots/' }
|
||||||
|
maven { url 'https://repo.opencollab.dev/maven-releases/' }
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'com.github.GeyserMC:MCProtocolLib:1.19-1'
|
implementation 'com.github.steveice10:mcprotocollib:1.19.1-SNAPSHOT'
|
||||||
implementation 'commons-cli:commons-cli:1.5.0'
|
implementation 'commons-cli:commons-cli:1.5.0'
|
||||||
implementation 'com.diogonunes:JColor:5.2.0'
|
implementation 'com.diogonunes:JColor:5.2.0'
|
||||||
implementation 'dnsjava:dnsjava:3.4.3'
|
implementation 'dnsjava:dnsjava:3.4.3'
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package me.creepermaxcz.mcbots;
|
package me.creepermaxcz.mcbots;
|
||||||
|
|
||||||
import com.github.steveice10.mc.protocol.MinecraftProtocol;
|
import com.github.steveice10.mc.protocol.MinecraftProtocol;
|
||||||
|
import com.github.steveice10.mc.protocol.data.game.LastSeenMessage;
|
||||||
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundLoginPacket;
|
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundLoginPacket;
|
||||||
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.player.ClientboundPlayerPositionPacket;
|
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.player.ClientboundPlayerPositionPacket;
|
||||||
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundChatCommandPacket;
|
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundChatCommandPacket;
|
||||||
@@ -16,7 +17,8 @@ import com.github.steveice10.packetlib.tcp.TcpClientSession;
|
|||||||
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.HashMap;
|
import java.util.ArrayList;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@@ -28,6 +30,7 @@ public class Bot extends Thread {
|
|||||||
private ProxyInfo proxy;
|
private ProxyInfo proxy;
|
||||||
private InetSocketAddress address;
|
private InetSocketAddress address;
|
||||||
private Session client;
|
private Session client;
|
||||||
|
private UUID uuid;
|
||||||
private boolean hasMainListener;
|
private boolean hasMainListener;
|
||||||
|
|
||||||
private double lastX, lastY, lastZ = -1;
|
private double lastX, lastY, lastZ = -1;
|
||||||
@@ -38,6 +41,7 @@ public class Bot extends Thread {
|
|||||||
this.nickname = nickname;
|
this.nickname = nickname;
|
||||||
this.address = address;
|
this.address = address;
|
||||||
this.proxy = proxy;
|
this.proxy = proxy;
|
||||||
|
this.uuid = UUID.randomUUID(); // Add random UUID for sending message.
|
||||||
|
|
||||||
Log.info("Creating bot", nickname);
|
Log.info("Creating bot", nickname);
|
||||||
protocol = new MinecraftProtocol(nickname);
|
protocol = new MinecraftProtocol(nickname);
|
||||||
@@ -106,29 +110,33 @@ public class Bot extends Thread {
|
|||||||
// The server console will print out that the message was "expired". To avoid this, set timeStamp as now.
|
// The server console will print out that the message was "expired". To avoid this, set timeStamp as now.
|
||||||
long timeStamp = Instant.now().toEpochMilli();
|
long timeStamp = Instant.now().toEpochMilli();
|
||||||
|
|
||||||
//send command
|
// Send command
|
||||||
if (text.startsWith("/")) {
|
if (text.startsWith("/")) {
|
||||||
|
|
||||||
client.send(new ServerboundChatCommandPacket(
|
client.send(new ServerboundChatCommandPacket(
|
||||||
text.substring(1), //remove slash on start
|
text.substring(1),
|
||||||
timeStamp,
|
timeStamp,
|
||||||
0,
|
0,
|
||||||
new HashMap<>(),
|
new ArrayList<>(),
|
||||||
true
|
true,
|
||||||
|
new ArrayList<>(),
|
||||||
|
null
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
//send chat message
|
// Send chat message
|
||||||
|
|
||||||
// From 1.19.1 or 1.19, the ServerboundChatPacket needs timestamp, salt and signed signature to generate packet.
|
// From 1.19.1 or 1.19, the ServerboundChatPacket needs timestamp, salt and signed signature to generate packet.
|
||||||
// tmpSignature will provide an empty byte array that can pretend it as signature.
|
// tmpSignature will provide an empty byte array that can pretend it as signature.
|
||||||
byte[] tmpSignature = new byte[0]; // Set it empty byte array.
|
|
||||||
// salt is set 0 since this is offline server and no body will check it.
|
// salt is set 0 since this is offline server and no body will check it.
|
||||||
client.send(new ServerboundChatPacket(text, timeStamp, 0, tmpSignature, true));
|
client.send(new ServerboundChatPacket(text,
|
||||||
|
timeStamp,
|
||||||
|
0,
|
||||||
|
new byte[0],
|
||||||
|
true,
|
||||||
|
new ArrayList<>(),
|
||||||
|
null
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public String getNickname() {
|
public String getNickname() {
|
||||||
return nickname;
|
return nickname;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,17 +20,34 @@ public class MainListener implements SessionListener {
|
|||||||
// From 1.19.1 (as well as 1.19), the class ClientboundChatPacket was removed.
|
// From 1.19.1 (as well as 1.19), the class ClientboundChatPacket was removed.
|
||||||
// Instead, they use ClientboundPlayerChatPacket and ClientboundSystemChatPacket for taking care of chat packets.
|
// Instead, they use ClientboundPlayerChatPacket and ClientboundSystemChatPacket for taking care of chat packets.
|
||||||
Component message = null;
|
Component message = null;
|
||||||
if(packet instanceof ClientboundPlayerChatPacket) {
|
Component sender;
|
||||||
// Since we are working on offline server, salt will be 0.
|
boolean chatPrintedOut = false;
|
||||||
message = ((ClientboundPlayerChatPacket) packet).getSignedContent(); // getSignedContent
|
|
||||||
} else if (packet instanceof ClientboundSystemChatPacket) {
|
if (packet instanceof ClientboundPlayerChatPacket) {
|
||||||
|
ClientboundPlayerChatPacket clientboundPlayerChatPacket = ((ClientboundPlayerChatPacket) packet);
|
||||||
|
message = clientboundPlayerChatPacket.getUnsignedContent();
|
||||||
|
sender = clientboundPlayerChatPacket.getName();
|
||||||
|
|
||||||
|
// Sometimes the message's body gets null.
|
||||||
|
// For example, some commands like /say makes the message content as null.
|
||||||
|
// However, the message exists as in getMessagePlain(), thus can retrieve message using the method.
|
||||||
|
if (message == null) { // When this message was null.
|
||||||
|
Log.chat(Utils.getFullText((TextComponent) sender, clientboundPlayerChatPacket.getMessagePlain(), Main.coloredChat));
|
||||||
|
} else { // When message exists.
|
||||||
|
Log.chat(Utils.getFullText((TextComponent) sender, (TextComponent) message, Main.coloredChat));
|
||||||
|
}
|
||||||
|
chatPrintedOut = true;
|
||||||
|
|
||||||
|
} else if (packet instanceof ClientboundSystemChatPacket) { // When this was SystemChat packet.
|
||||||
message = ((ClientboundSystemChatPacket) packet).getContent();
|
message = ((ClientboundSystemChatPacket) packet).getContent();
|
||||||
}
|
}
|
||||||
//Log.chat(message.toString());
|
|
||||||
if (message instanceof TextComponent) {
|
// For output of commands, this is the case where this program prints out the message to user.
|
||||||
|
if (message instanceof TextComponent && !chatPrintedOut) {
|
||||||
TextComponent msg = (TextComponent) message;
|
TextComponent msg = (TextComponent) message;
|
||||||
Log.chat(Utils.getFullText(msg, Main.coloredChat));
|
Log.chat(Utils.getFullText(msg, Main.coloredChat));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message instanceof TranslatableComponent) {
|
if (message instanceof TranslatableComponent) {
|
||||||
TranslatableComponent msg = (TranslatableComponent) message;
|
TranslatableComponent msg = (TranslatableComponent) message;
|
||||||
Log.chat("[T]", Utils.translate(msg));
|
Log.chat("[T]", Utils.translate(msg));
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ import net.kyori.adventure.text.Component;
|
|||||||
import net.kyori.adventure.text.TextComponent;
|
import net.kyori.adventure.text.TextComponent;
|
||||||
import net.kyori.adventure.text.TranslatableComponent;
|
import net.kyori.adventure.text.TranslatableComponent;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import net.kyori.adventure.text.format.Style;
|
||||||
import net.kyori.adventure.text.format.TextDecoration;
|
import net.kyori.adventure.text.format.TextDecoration;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.w3c.dom.Text;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
@@ -45,7 +48,83 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A static function that gets full text from text components.
|
||||||
|
* From 1.19.1 the protocol does not include sender's information in the message.
|
||||||
|
* Thus, we need to merge two TextComponent (sender and message) and generate a text that consists of sent message.
|
||||||
|
* This means using old getFullText will just send the message. Which does NOT contain username who sent this message
|
||||||
|
* @param sender The TextComponent for sender.
|
||||||
|
* @param message The TextComponent for message.
|
||||||
|
* @param colored Whether if this output was colored or not.
|
||||||
|
* @return Returns String in "Username : Message" format.
|
||||||
|
*/
|
||||||
|
public static String getFullText(@Nullable TextComponent sender, TextComponent message, boolean colored) {
|
||||||
|
if (colored) { // Check if we are using colors.
|
||||||
|
Style messageStyle = message.style();
|
||||||
|
StringBuilder outString = new StringBuilder();
|
||||||
|
|
||||||
|
if (sender != null) {
|
||||||
|
Style senderStyle = sender.style();
|
||||||
|
if (senderStyle.color() != null) { // Generate sender text with color.
|
||||||
|
NamedTextColor color = (NamedTextColor) senderStyle.color();
|
||||||
|
outString.append(colorize(sender.content(), getColor(color)));
|
||||||
|
} else {
|
||||||
|
outString.append(sender.content());
|
||||||
|
}
|
||||||
|
outString.append(" : "); // Add delimiter as : between sender and message.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (messageStyle.color() != null) { // Generate message text with color.
|
||||||
|
NamedTextColor color = (NamedTextColor) messageStyle.color();
|
||||||
|
outString.append(colorize(message.content(), getColor(color)));
|
||||||
|
} else {
|
||||||
|
outString.append(message.content());
|
||||||
|
}
|
||||||
|
|
||||||
|
return outString.toString();
|
||||||
|
} else { // When color formatting was disabled.
|
||||||
|
if (sender == null)
|
||||||
|
return message.content();
|
||||||
|
else
|
||||||
|
return sender.content() + " : " + message.content();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A static function that gets full text from text components.
|
||||||
|
* From 1.19.1 the protocol does not include sender's information in the message.
|
||||||
|
* Sometimes the message as component is null. When this happens, this method will show message as String.
|
||||||
|
* Since this is just a String being represented, this will not have formatting on String.
|
||||||
|
* @param sender The TextComponent for sender.
|
||||||
|
* @param message The TextComponent for message.
|
||||||
|
* @param colored Whether if this output was colored or not.
|
||||||
|
* @return Returns String in "Username : Message" format.
|
||||||
|
*/
|
||||||
|
public static String getFullText(@Nullable TextComponent sender, String message, boolean colored) {
|
||||||
|
if (colored) { // Check if we are using colors.
|
||||||
|
StringBuilder outString = new StringBuilder();
|
||||||
|
|
||||||
|
if (sender != null) {
|
||||||
|
Style senderStyle = sender.style();
|
||||||
|
if (senderStyle.color() != null) { // Generate sender text with color.
|
||||||
|
NamedTextColor color = (NamedTextColor) senderStyle.color();
|
||||||
|
outString.append(colorize(sender.content(), getColor(color)));
|
||||||
|
} else {
|
||||||
|
outString.append(sender.content());
|
||||||
|
}
|
||||||
|
outString.append(" : "); // Add delimiter as : between sender and message.
|
||||||
|
}
|
||||||
|
|
||||||
|
outString.append(message);
|
||||||
|
return outString.toString();
|
||||||
|
} else { // When color formatting was disabled.
|
||||||
|
if (sender == null)
|
||||||
|
return message;
|
||||||
|
else
|
||||||
|
return sender.content() + " : " + message;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Attribute getColor(NamedTextColor color) {
|
public static Attribute getColor(NamedTextColor color) {
|
||||||
|
|||||||
Reference in new issue
Block a user