Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code improvements & fix response packet size #22

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from
Open
142 changes: 71 additions & 71 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
# minecraft-rcon
[![](https://jitpack.io/v/MrGraversen/minecraft-rcon.svg)](https://jitpack.io/#MrGraversen/minecraft-rcon)
[![](https://jitpack.io/v/WarningImHack3r/minecraft-rcon.svg)](https://jitpack.io/#WarningImHack3r/minecraft-rcon)

A MineCraft RCON client in Java.
A Minecraft RCON client in Java.

## About
The client is made to eventually automate some administrative actions of a MineCraft server. Every MineCraft command is exposed over RCON, and made available using this client.
The client is made to eventually automate some administrative actions of a Minecraft server. Every Minecraft command is exposed over RCON, and made available using this client.

Some of the RCON commands are fairly complex in structure; this client will provide convenience methods and builders to help developers construct the correct commands to get the job done.

The entire RCON command libarary is *not* implemeneted, but will be updated along the way. Feel free to PR and contribute with implementations of missing commands.
The entire RCON command library is *not* implemented, but will be updated along the way. Feel free to PR and contribute with implementations of missing commands.

This project will also provide some convenience code for working with the MineCraft target selectors (e.g. `@a` and so on), the 16 colors of the game, different labels (time of day, game rules, etc.) and other constants.
This project will also provide some convenience code for working with the Minecraft target selectors (e.g. `@a` and so on), the 16 colors of the game, different labels (time of day, game rules, etc.) and other constants.

Using the convenience methods will synchronously access the RCON stack, but it is also possible to send raw, asynchronous RCON messages.

**Warning:** There's a long-standing issue with MineCraft RCON which, for some reason, causes RCON commands to execute from a not-thread-safe queue (whereas the normal MineCraft action queue execute from a thread-safe queue) (https://bugs.mojang.com/browse/MC-72390). Sometimes when executing many commands rapidly, e.g. giving a player *many* items, the game server will throw a `java.util.ConcurrentModificationException`, which sometimes casues it to crash entirely.
I have, however, only seen this happening when I was really pushing it.

### What is RCON?
Originally invented by Valve for the Source Engine, it's a TCP/IP protocol for remote administration of game servers. The MineCraft RCON protocol is more-or-less an implementation of the Source Engine RCON protocol. It was introduced in beta update 1.9.
Originally invented by Valve for the Source Engine, it's a TCP/IP protocol for remote administration of game servers. The Minecraft RCON protocol is more-or-less an implementation of the Source Engine RCON protocol. It was introduced in beta update 1.9 prerelease 4.

## Install

Expand Down Expand Up @@ -57,70 +54,74 @@ Once done, RCON is available using this client.

## Example 1

The example is also available in the `io.graversen.minecraft.rcon.examples.Example1.java` class.
The example is also available in the [`Example1.java`](src/main/java/examples/Example1.java) class. Other examples are available in the [`examples`](src/main/java/examples) folder.

```java
// Define a simple MinecraftRconService
// Assuming Minecraft server is running on localhost and password set to "test"
// If no port is specified, the default Minecraft RCON port will be used
final MinecraftRconService minecraftRconService = new MinecraftRconService(
RconDetails.localhost("test"),
ConnectOptions.defaults()
);

// Let's go!
minecraftRconService.connectBlocking(Duration.ofSeconds(3));

// After connecting, we can (crudely) fetch the underlying Minecraft RCON provider
final MinecraftRcon minecraftRcon = minecraftRconService.minecraftRcon().orElseThrow(IllegalStateException::new);

// Build a TellRaw command - first half of the desired message
final TellRawCommand tellRawCommand1 = new TellRawCommandBuilder()
.targeting(Selectors.ALL_PLAYERS)
.withText("It's dangerous to go alone - ")
.withColor(Colors.GRAY)
.italic()
.build();


// Build another TellRaw command - other half of the message
final TellRawCommand tellRawCommand2 = new TellRawCommandBuilder()
.targeting(Selectors.ALL_PLAYERS)
.withText("Take this!")
.withColor(Colors.DARK_AQUA)
.italic()
.build();

// We are able to transparently stitch together multiple 'tellraw' commands,
// combining their styles and texts into a composite viewing
final TellRawCompositeCommand tellRawCompositeCommand = new TellRawCompositeCommand(List.of(tellRawCommand1, tellRawCommand2));

// Let's also add a nice title to the players' screens
final TitleCommand titleCommand = new TitleCommandBuilder()
.targeting(Selectors.ALL_PLAYERS)
.atPosition(TitlePositions.TITLE)
.withColor(Colors.GREEN)
.withText("Welcome!")
.build();

// We'll give everyone a diamond sword - it's dangerous without
final GiveCommand giveCommand = new GiveCommand(
Selectors.ALL_PLAYERS.getSelectorString(), new MinecraftItem("diamond_sword"), 1
);

// Fire away!
minecraftRcon.sendAsync(tellRawCompositeCommand, titleCommand, giveCommand);

// Just for fun, let's also change some other things

// Set time of day to noon and clear weather - nice and sunny
final TimeCommand timeCommand = new TimeCommand(TimeLabels.NOON);
final WeatherCommand weatherCommand = new WeatherCommand(Weathers.CLEAR, Duration.ofHours(1).toSeconds());
minecraftRcon.sendAsync(timeCommand, weatherCommand);

// The players hate it when their creations are blown up by Creepers, lets' help them
final ICommand disableMobGriefing = GameRulesCommands.setGameRule(GameRules.MOB_GRIEFING, false);
minecraftRcon.sendAsync(disableMobGriefing);
class Example {
public static void main(String[] args) {
final MinecraftRconService minecraftRconService = new MinecraftRconService(
RconDetails.localhost("test"),
ConnectOptions.defaults()
);

// Let's go!
minecraftRconService.connectBlocking(Duration.ofSeconds(3));

// After connecting, we can (crudely) fetch the underlying Minecraft RCON provider
final MinecraftRcon minecraftRcon = minecraftRconService.minecraftRcon().orElseThrow(IllegalStateException::new);

// Build a TellRaw command – first half of the desired message
final TellRawCommand tellRawCommand1 = new TellRawCommandBuilder()
.targeting(Selectors.ALL_PLAYERS)
.withText("It's dangerous to go alone - ")
.withColor(Colors.GRAY)
.italic()
.build();


// Build another TellRaw command – other half of the message
final TellRawCommand tellRawCommand2 = new TellRawCommandBuilder()
.targeting(Selectors.ALL_PLAYERS)
.withText("Take this!")
.withColor(Colors.DARK_AQUA)
.italic()
.build();

// We can transparently stitch together multiple 'tellraw' commands,
// combining their styles and texts into a composite viewing.
final TellRawCompositeCommand tellRawCompositeCommand = new TellRawCompositeCommand(List.of(tellRawCommand1, tellRawCommand2));

// Let's also add a nice title to the players' screens.
final TitleCommand titleCommand = new TitleCommandBuilder()
.targeting(Selectors.ALL_PLAYERS)
.atPosition(TitlePositions.TITLE)
.withColor(Colors.GREEN)
.withText("Welcome!")
.build();

// We'll give everyone a diamond sword - it's dangerous without.
final GiveCommand giveCommand = new GiveCommand(
Selectors.ALL_PLAYERS.getSelectorString(), new MinecraftItem("diamond_sword"), 1
);

// Fire away!
minecraftRcon.sendAsync(tellRawCompositeCommand, titleCommand, giveCommand);

// Just for fun, let's also change some other things

// Set time of day to noon and clear weather – nice and sunny.
final TimeCommand timeCommand = new TimeCommand(TimeLabels.NOON);
final WeatherCommand weatherCommand = new WeatherCommand(Weathers.CLEAR, Duration.ofHours(1).toSeconds());
minecraftRcon.sendAsync(timeCommand, weatherCommand);

// The players hate it when their creations are blown up by Creepers, lets' help them.
final ICommand disableMobGriefing = GameRulesCommands.setGameRule(GameRules.MOB_GRIEFING, false);
minecraftRcon.sendAsync(disableMobGriefing);
}
}
```

**In-game result:**
Expand All @@ -134,11 +135,10 @@ minecraftRcon.sendAsync(disableMobGriefing);
14:07:27.718 [pool-1-thread-2] INFO io.graversen.minecraft.rcon.MinecraftClient - Initialized with connection tuple 'localhost:25575'
14:07:27.718 [pool-1-thread-2] DEBUG io.graversen.minecraft.rcon.MinecraftClient - Authenticating...
14:07:27.721 [pool-1-thread-2] INFO io.graversen.minecraft.rcon.MinecraftClient - Connection success!
14:07:27.770 [pool-2-thread-1] DEBUG io.graversen.minecraft.rcon.MinecraftClient - Sending command: tellraw @a [{"text":"It\u0027s dangerous to go alone - ","bold":false,"italic":true,"underlined":false,"striketrough":false,"obfuscated":false,"color":"gray"},{"text":"Take this!","bold":false,"italic":true,"underlined":false,"striketrough":false,"obfuscated":false,"color":"dark_aqua"}]
14:07:27.771 [pool-2-thread-1] DEBUG io.graversen.minecraft.rcon.MinecraftClient - Sending command: title @a title {"text":"Welcome!","bold":false,"italic":false,"underlined":false,"striketrough":false,"obfuscated":false,"color":"green"}
14:07:27.770 [pool-2-thread-1] DEBUG io.graversen.minecraft.rcon.MinecraftClient - Sending command: tellraw @a [{"text":"It\u0027s dangerous to go alone - ","bold":false,"italic":true,"underlined":false,"strikethrough":false,"obfuscated":false,"color":"gray"},{"text":"Take this!","bold":false,"italic":true,"underlined":false,"strikethrough":false,"obfuscated":false,"color":"dark_aqua"}]
14:07:27.771 [pool-2-thread-1] DEBUG io.graversen.minecraft.rcon.MinecraftClient - Sending command: title @a title {"text":"Welcome!","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false,"color":"green"}
14:07:27.771 [pool-2-thread-1] DEBUG io.graversen.minecraft.rcon.MinecraftClient - Sending command: give @a minecraft:diamond_sword 1
14:07:27.782 [pool-2-thread-1] DEBUG io.graversen.minecraft.rcon.MinecraftClient - Sending command: time set 6000
14:07:27.782 [pool-2-thread-1] DEBUG io.graversen.minecraft.rcon.MinecraftClient - Sending command: weather clear 3600
14:07:27.798 [pool-2-thread-1] DEBUG io.graversen.minecraft.rcon.MinecraftClient - Sending command: gamerule mobGriefing false

```
61 changes: 14 additions & 47 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,56 +6,25 @@

<groupId>io.graversen</groupId>
<artifactId>minecraft-rcon</artifactId>
<version>1.0-SNAPSHOT</version>
<version>1.0.0</version>

<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<slf4j.version>2.17.1</slf4j.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>20</maven.compiler.source>
<maven.compiler.target>20</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.3</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>29.0-jre</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.8</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>

<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>${slf4j.version}</version>
<version>2.10.1</version>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.4.0</version>
<version>5.9.3</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand All @@ -64,15 +33,13 @@
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>20</source>
<target>20</target>
<compilerArgs>--enable-preview</compilerArgs>
</configuration>
</plugin>

<plugin>
Expand All @@ -95,4 +62,4 @@
</plugins>
</build>

</project>
</project>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.graversen.minecraft.rcon.examples;
package examples;

import io.graversen.minecraft.rcon.MinecraftRcon;
import io.graversen.minecraft.rcon.commands.GameRulesCommands;
Expand All @@ -20,11 +20,12 @@
import java.util.List;

public class Example1 {

public static void main(String[] args) {

// Define a simple MinecraftRconService
// Assuming Minecraft server is running on localhost and password set to "test"
// If no port is specified, the default Minecraft RCON port will be used
// If no port is specified, the default Minecraft RCON port will be used.
final MinecraftRconService minecraftRconService = new MinecraftRconService(
RconDetails.localhost("test"),
ConnectOptions.defaults()
Expand All @@ -33,55 +34,55 @@ public static void main(String[] args) {
// Let's go!
minecraftRconService.connectBlocking(Duration.ofSeconds(3));

// After connecting, we can (crudely) fetch the underlying Minecraft RCON provider
// After connecting, we can (crudely) fetch the underlying Minecraft RCON provider.
final MinecraftRcon minecraftRcon = minecraftRconService.minecraftRcon().orElseThrow(IllegalStateException::new);

// Build a TellRaw command - first half of the desired message
// Build a TellRaw command first half of the desired message
final TellRawCommand tellRawCommand1 = new TellRawCommandBuilder()
.targeting(Selectors.ALL_PLAYERS)
.targeting(Selector.ALL_PLAYERS)
.withText("It's dangerous to go alone - ")
.withColor(Colors.GRAY)
.withColor(Color.GRAY)
.italic()
.build();


// Build another TellRaw command - other half of the message
// Build another TellRaw command other half of the message
final TellRawCommand tellRawCommand2 = new TellRawCommandBuilder()
.targeting(Selectors.ALL_PLAYERS)
.targeting(Selector.ALL_PLAYERS)
.withText("Take this!")
.withColor(Colors.DARK_AQUA)
.withColor(Color.DARK_AQUA)
.italic()
.build();

// We are able to transparently stitch together multiple 'tellraw' commands,
// combining their styles and texts into a composite viewing
// We can transparently stitch together multiple 'tellraw' commands,
// combining their styles and texts into a composite viewing.
final TellRawCompositeCommand tellRawCompositeCommand = new TellRawCompositeCommand(List.of(tellRawCommand1, tellRawCommand2));

// Let's also add a nice title to the players' screens
// Let's also add a nice title to the players' screens.
final TitleCommand titleCommand = new TitleCommandBuilder()
.targeting(Selectors.ALL_PLAYERS)
.atPosition(TitlePositions.TITLE)
.withColor(Colors.GREEN)
.targeting(Selector.ALL_PLAYERS)
.atPosition(TitlePosition.TITLE)
.withColor(Color.GREEN)
.withText("Welcome!")
.build();

// We'll give everyone a diamond sword - it's dangerous without
// We'll give everyone a diamond sword it is dangerous without.
final GiveCommand giveCommand = new GiveCommand(
Target.selector(Selectors.ALL_PLAYERS), new MinecraftItem("diamond_sword"), null, 1
Target.selector(Selector.ALL_PLAYERS), new MinecraftItem("diamond_sword"), null, 1
);

// Fire away!
minecraftRcon.sendAsync(tellRawCompositeCommand, titleCommand, giveCommand);

// Just for fun, let's also change some other things

// Set time of day to noon and clear weather - nice and sunny
final TimeCommand timeCommand = new TimeCommand(TimeLabels.NOON);
final WeatherCommand weatherCommand = new WeatherCommand(Weathers.CLEAR, Duration.ofHours(1).toSeconds());
// Set time of day to noon and clear weather nice and sunny.
final TimeCommand timeCommand = new TimeCommand(TimeLabel.NOON);
final WeatherCommand weatherCommand = new WeatherCommand(Weather.CLEAR, Duration.ofHours(1).toSeconds());
minecraftRcon.sendAsync(timeCommand, weatherCommand);

// The players hate it when their creations are blown up by Creepers, lets' help them
final ICommand disableMobGriefing = GameRulesCommands.setGameRule(GameRules.MOB_GRIEFING, false);
// The players hate it when their creations are blown up by Creepers, lets' help them.
final ICommand disableMobGriefing = GameRulesCommands.setGameRule(GameRule.MOB_GRIEFING, false);
minecraftRcon.sendAsync(disableMobGriefing);
}
}
Loading