11 Commits
Author SHA1 Message Date
CreeperMaxCZ 3311f0889a Merge pull request #73 from Norbiros/fix/chat
Important chat bug fixes
2023-08-14 22:48:38 +02:00
CreeperMaxCZ c05b7ffef4 Merge pull request #72 from Norbiros/feat/use-jline
feat: Switch to JLine.
2023-08-14 22:48:08 +02:00
CreeperMaxCZ d8c2139df6 Merge branch 'main' into feat/use-jline 2023-08-14 22:48:00 +02:00
CreeperMaxCZ 5c12776348 Merge pull request #71 from ShayanGsh/main
Added support for Minecraft 1.20
2023-08-14 22:46:00 +02:00
Norbiros 896c320dd7 fix: Chat bug fixes 2023-08-13 00:22:23 +02:00
Shayan d8cd699012 Merging 2023-08-09 18:01:45 +00:00
Shayan d550c69583 Updated Readme for 1.20 and increased mc-bots' version in gradle 2023-08-09 17:57:06 +00:00
Norbiros 4c239b9b5b feat: Switch to JLine. 2023-08-08 13:13:32 +02:00
Shayan f82eb34588 feat: Support for 1.20! 2023-08-01 20:49:16 +03:30
CreeperMaxCZ ea0efd88bb Merge pull request #69 from Norbiros/main
Proper support fo 1.19.4!
2023-07-15 14:13:36 +02:00
Norbiros 1fbadaba9c feat: Support 1.19.4! 2023-07-02 10:55:34 +02:00
7 changed files with 65 additions and 137 deletions

No files matched your search

+1 -1
View File
@@ -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.1 (supports 1.19.2). ✔️ The MC version of the bots is 1.20 (supports 1.20.x).
For older MC versions please look in the [releases](https://github.com/crpmax/mc-bots/releases " releases"). For older MC versions please look in the [releases](https://github.com/crpmax/mc-bots/releases " releases").
## 🆒 Features ## 🆒 Features
+3 -2
View File
@@ -3,7 +3,7 @@ plugins {
} }
group 'me.creepermaxcz.mc-bots' group 'me.creepermaxcz.mc-bots'
version '1.2.6' version '1.2.8'
sourceCompatibility = 1.8 sourceCompatibility = 1.8
targetCompatibility = 1.8 targetCompatibility = 1.8
@@ -16,11 +16,12 @@ repositories {
} }
dependencies { dependencies {
implementation 'com.github.steveice10:mcprotocollib:1.19.2-1' implementation 'com.github.steveice10:mcprotocollib:1.20-1'
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'
implementation 'org.slf4j:slf4j-nop:1.7.32' implementation 'org.slf4j:slf4j-nop:1.7.32'
implementation 'org.jline:jline:3.22.0'
} }
jar { jar {
Vendored Regular → Executable
View File
File mode changed.
+13 -18
View File
@@ -7,7 +7,6 @@ import com.github.steveice10.mc.protocol.MinecraftProtocol;
import com.github.steveice10.mc.protocol.data.UnexpectedEncryptionException; import com.github.steveice10.mc.protocol.data.UnexpectedEncryptionException;
import com.github.steveice10.mc.protocol.data.game.ClientCommand; import com.github.steveice10.mc.protocol.data.game.ClientCommand;
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.ClientboundRespawnPacket;
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.player.ClientboundPlayerCombatKillPacket; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.player.ClientboundPlayerCombatKillPacket;
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;
@@ -24,9 +23,7 @@ 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.ArrayList; import java.util.*;
import java.util.Timer;
import java.util.TimerTask;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -126,11 +123,9 @@ public class Bot extends Thread {
// Do not write disconnect reason if disconnected by command // Do not write disconnect reason if disconnected by command
if (!manualDisconnecting) { if (!manualDisconnecting) {
//Log.info(" -> " + event.getReason()); // Fix broken reason string by finding the content with regex
//fix broken reason string by finding the content with regex
Pattern pattern = Pattern.compile("content=\"(.*?)\""); Pattern pattern = Pattern.compile("content=\"(.*?)\"");
Matcher matcher = pattern.matcher(event.getReason()); Matcher matcher = pattern.matcher(String.valueOf(event.getReason()));
StringBuilder reason = new StringBuilder(); StringBuilder reason = new StringBuilder();
while (matcher.find()) { while (matcher.find()) {
@@ -169,24 +164,24 @@ public class Bot extends Thread {
client.send(new ServerboundChatCommandPacket( client.send(new ServerboundChatCommandPacket(
text.substring(1), text.substring(1),
timeStamp, timeStamp,
0L,
Collections.emptyList(),
0, 0,
new ArrayList<>(), new BitSet()
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.
// 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, client.send(new ServerboundChatPacket(
text,
Instant.now().toEpochMilli(),
0L,
null,
0, 0,
new byte[0], new BitSet()
true,
new ArrayList<>(),
null
)); ));
} }
} }
+23 -17
View File
@@ -7,6 +7,11 @@ import com.github.steveice10.mc.auth.util.HTTP;
import com.github.steveice10.mc.protocol.data.status.ServerStatusInfo; import com.github.steveice10.mc.protocol.data.status.ServerStatusInfo;
import com.github.steveice10.packetlib.ProxyInfo; import com.github.steveice10.packetlib.ProxyInfo;
import org.apache.commons.cli.*; import org.apache.commons.cli.*;
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
import org.jline.reader.UserInterruptException;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
import org.xbill.DNS.Lookup; import org.xbill.DNS.Lookup;
import org.xbill.DNS.Record; import org.xbill.DNS.Record;
import org.xbill.DNS.SRVRecord; import org.xbill.DNS.SRVRecord;
@@ -390,12 +395,22 @@ public class Main {
}, 1000L, 500L); }, 1000L, 500L);
} }
Terminal terminal = TerminalBuilder.builder().build();
LineReader lineReader = LineReaderBuilder.builder()
.terminal(terminal)
.parser(new org.jline.reader.impl.DefaultParser())
.build();
Scanner scanner = new Scanner(System.in);
prompt = "ALL"; prompt = "ALL";
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
while (true) {
String line;
try {
line = lineReader.readLine();
} catch (UserInterruptException e) {
System.exit(0);
break;
}
if (line.isEmpty()) { if (line.isEmpty()) {
System.out.print(getPrompt() + "> "); System.out.print(getPrompt() + "> ");
continue; continue;
@@ -456,16 +471,12 @@ public class Main {
// List command // List command
else if (commandName.equalsIgnoreCase("list") || commandName.equalsIgnoreCase("ls")) else if (commandName.equalsIgnoreCase("list") || commandName.equalsIgnoreCase("ls")) {
{
Log.info("There are " + bots.size() + " bots connected:"); Log.info("There are " + bots.size() + " bots connected:");
for (Bot bot : bots) { for (Bot bot : bots) {
Log.info(bot.getNickname(), bot.hasMainListener() ? "[MainListener]" : ""); Log.info(bot.getNickname(), bot.hasMainListener() ? "[MainListener]" : "");
} }
} } else if (commandName.equalsIgnoreCase("leave") || commandName.equalsIgnoreCase("exit")) {
else if (commandName.equalsIgnoreCase("leave") || commandName.equalsIgnoreCase("exit"))
{
int limit = -1; int limit = -1;
if (split.length >= 2) { if (split.length >= 2) {
try { try {
@@ -496,17 +507,12 @@ public class Main {
bot.disconnect(); bot.disconnect();
} }
} }
} } else {
else {
Log.warn("Invalid command"); Log.warn("Invalid command");
} }
} } else if (controlledBots.size() > 0) {
else if (controlledBots.size() > 0) {
controlledBots.forEach(bot -> bot.sendChat(line)); controlledBots.forEach(bot -> bot.sendChat(line));
} } else {
else {
bots.forEach(bot -> bot.sendChat(line)); bots.forEach(bot -> bot.sendChat(line));
} }
@@ -31,9 +31,9 @@ public class MainListener implements SessionListener {
// Sometimes the message's body gets null. // Sometimes the message's body gets null.
// For example, some commands like /say makes the message content as 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. // However, the message exists as in getMessagePlain(), thus can retrieve message using the method.
if (message == null) { // When this message was null. if (message == null) {
Log.chat(Utils.getFullText((TextComponent) sender, clientboundPlayerChatPacket.getMessagePlain(), Main.coloredChat)); Log.chat(Utils.getFullText((TextComponent) sender, Component.text(clientboundPlayerChatPacket.getContent()), Main.coloredChat));
} else { // When message exists. } else {
Log.chat(Utils.getFullText((TextComponent) sender, (TextComponent) message, Main.coloredChat)); Log.chat(Utils.getFullText((TextComponent) sender, (TextComponent) message, Main.coloredChat));
} }
chatPrintedOut = true; chatPrintedOut = true;
+22 -96
View File
@@ -7,11 +7,10 @@ import com.google.gson.JsonParser;
import net.kyori.adventure.text.Component; 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.Style; import net.kyori.adventure.text.format.Style;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.format.TextDecoration; import net.kyori.adventure.text.format.TextDecoration;
import org.jetbrains.annotations.Nullable; 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;
@@ -28,6 +27,7 @@ public class Utils {
public static String getFullText(TextComponent message, boolean colored) { public static String getFullText(TextComponent message, boolean colored) {
if (message.children().size() > 0) { if (message.children().size() > 0) {
StringBuilder text = new StringBuilder(); StringBuilder text = new StringBuilder();
text.append(parseColor(message, colored));
for (Component child : message.children()) { for (Component child : message.children()) {
if (child instanceof TextComponent) { if (child instanceof TextComponent) {
text.append(getFullText((TextComponent) child, colored)); text.append(getFullText((TextComponent) child, colored));
@@ -38,15 +38,7 @@ public class Utils {
} }
return text.toString(); return text.toString();
} else { } else {
String out = message.content(); return parseColor(message, colored);
if (colored) {
if (message.style().color() != null) {
List<Attribute> formats = new ArrayList<>(getFormats(message.style().decorations()));
formats.add(getColor((NamedTextColor) message.style().color()));
out = colorize(out, formats.toArray(new Attribute[0]));
}
}
return out;
} }
} }
@@ -55,36 +47,35 @@ public class Utils {
* From 1.19.1 the protocol does not include sender's information in the message. * 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. * 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 * 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 sender The TextComponent for sender.
* @param message The TextComponent for message. * @param message The TextComponent for message.
* @param colored Whether if this output was colored or not. * @param colored Whether if this output was colored or not.
* @return Returns String in "Username : Message" format. * @return Returns String in "Username : Message" format.
*/ */
public static String getFullText(@Nullable TextComponent sender, TextComponent message, boolean colored) { public static String getFullText(@Nullable TextComponent sender, TextComponent message, boolean colored) {
if (colored) { // Check if we are using colors. if (colored) {
Style messageStyle = message.style(); Style messageStyle = message.style();
StringBuilder outString = new StringBuilder(); StringBuilder outString = new StringBuilder();
if (sender != null) { if (sender != null) {
Style senderStyle = sender.style(); Style senderStyle = sender.style();
if (senderStyle.color() != null) { // Generate sender text with color. if (senderStyle.color() != null) { // Generate sender text with color.
NamedTextColor color = (NamedTextColor) senderStyle.color(); outString.append(colorizeText(sender.content(), senderStyle.color()));
outString.append(colorize(sender.content(), getColor(color)));
} else { } else {
outString.append(sender.content()); outString.append(sender.content());
} }
outString.append(" : "); // Add delimiter as : between sender and message. outString.append(" : "); // Add delimiter as : between sender and message.
} }
if (messageStyle.color() != null) { // Generate message text with color. if (messageStyle.color() != null && sender != null) { // Generate message text with color.
NamedTextColor color = (NamedTextColor) messageStyle.color(); outString.append(colorizeText(sender.content(), messageStyle.color()));
outString.append(colorize(message.content(), getColor(color)));
} else { } else {
outString.append(message.content()); outString.append(message.content());
} }
return outString.toString(); return outString.toString();
} else { // When color formatting was disabled. } else {
if (sender == null) if (sender == null)
return message.content(); return message.content();
else else
@@ -92,83 +83,21 @@ public class Utils {
} }
} }
/** private static String parseColor(TextComponent message, boolean colored) {
* A static function that gets full text from text components. String out = message.content();
* From 1.19.1 the protocol does not include sender's information in the message. if (colored) {
* Sometimes the message as component is null. When this happens, this method will show message as String. if (message.style().color() != null) {
* Since this is just a String being represented, this will not have formatting on String. List<Attribute> formats = new ArrayList<>(getFormats(message.style().decorations()));
* @param sender The TextComponent for sender. TextColor color = message.style().color();
* @param message The TextComponent for message. formats.add(Attribute.TEXT_COLOR(color.red(), color.green(), color.blue()));
* @param colored Whether if this output was colored or not. out = colorize(out, formats.toArray(new Attribute[0]));
* @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;
} }
return out;
} }
public static Attribute getColor(NamedTextColor color) { private static String colorizeText(String text, TextColor color) {
switch (color.toString()) { return colorize(text, Attribute.TEXT_COLOR(color.red(), color.green(), color.blue()));
case "red":
return Attribute.BRIGHT_RED_TEXT();
case "dark_red":
return Attribute.RED_TEXT();
case "blue":
return Attribute.BRIGHT_BLUE_TEXT();
case "dark_blue":
return Attribute.BLUE_TEXT();
case "white":
return Attribute.BRIGHT_WHITE_TEXT();
case "black":
return Attribute.BLACK_TEXT();
case "gray":
return Attribute.WHITE_TEXT();
case "dark_gray":
return Attribute.BRIGHT_BLACK_TEXT();
case "aqua":
return Attribute.BRIGHT_CYAN_TEXT();
case "dark_aqua":
return Attribute.CYAN_TEXT();
case "yellow":
return Attribute.BRIGHT_YELLOW_TEXT();
case "gold":
return Attribute.YELLOW_TEXT();
case "green":
return Attribute.BRIGHT_GREEN_TEXT();
case "dark_green":
return Attribute.GREEN_TEXT();
case "purple":
return Attribute.BRIGHT_MAGENTA_TEXT();
case "dark_purple":
return Attribute.MAGENTA_TEXT();
}
return Attribute.NONE();
} }
public static List<Attribute> getFormats(Map<TextDecoration, TextDecoration.State> decorations) { public static List<Attribute> getFormats(Map<TextDecoration, TextDecoration.State> decorations) {
@@ -191,7 +120,6 @@ public class Utils {
return list; return list;
} }
public static String translate(TranslatableComponent message) { public static String translate(TranslatableComponent message) {
if (lang == null) { if (lang == null) {
try { try {
@@ -217,8 +145,7 @@ public class Utils {
for (Component component : message.args()) { for (Component component : message.args()) {
if (component instanceof TranslatableComponent) { if (component instanceof TranslatableComponent) {
placeholders.add(translate((TranslatableComponent) component)); placeholders.add(translate((TranslatableComponent) component));
} } else if (component instanceof TextComponent) {
else if (component instanceof TextComponent) {
placeholders.add(getFullText((TextComponent) component, false)); placeholders.add(getFullText((TextComponent) component, false));
} }
} }
@@ -228,5 +155,4 @@ public class Utils {
return base; return base;
} }
} }
} }