mirror of
https://github.com/Minestom/Minestom.git
synced 2025-01-16 21:21:22 +01:00
Implement OpenToLAN system
This commit is contained in:
parent
009f7cb1da
commit
01fe452783
@ -39,7 +39,7 @@ public class ServerListPingEvent extends Event implements CancellableEvent {
|
||||
* @param version the ping version to respond with
|
||||
*/
|
||||
public ServerListPingEvent(@Nullable PlayerConnection connection, @NotNull ServerListPingVersion version) {
|
||||
//noinspection deprecation we need to continue doing this until the consumer is removed
|
||||
//noinspection deprecation we need to continue doing this until the consumer is removed - todo remove
|
||||
ResponseDataConsumer consumer = MinecraftServer.getResponseDataConsumer();
|
||||
this.responseData = new ResponseData();
|
||||
|
||||
@ -97,7 +97,8 @@ public class ServerListPingEvent extends Event implements CancellableEvent {
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancelling this event will cause you server to appear offline in the vanilla server list.
|
||||
* Cancelling this event will cause the server to appear offline in the vanilla server list.
|
||||
* Note that this will have no effect if the ping version is {@link ServerListPingVersion#OPEN_TO_LAN}.
|
||||
*
|
||||
* @param cancel true if the event should be cancelled, false otherwise
|
||||
*/
|
||||
|
135
src/main/java/net/minestom/server/extras/lan/OpenToLAN.java
Normal file
135
src/main/java/net/minestom/server/extras/lan/OpenToLAN.java
Normal file
@ -0,0 +1,135 @@
|
||||
package net.minestom.server.extras.lan;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.event.server.ServerListPingEvent;
|
||||
import net.minestom.server.timer.Task;
|
||||
import net.minestom.server.utils.time.Cooldown;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static net.minestom.server.ping.ServerListPingVersion.OPEN_TO_LAN;
|
||||
|
||||
/**
|
||||
* Utility class to manage opening the server to LAN. Note that this <b>doesn't</b> actually
|
||||
* open your server to LAN if it isn't already visible to anyone on your local network.
|
||||
* Instead it simply sends the packets needed to trick the Minecraft client into thinking
|
||||
* that this is a single-player world that has been opened to LANfor it to be displayed on
|
||||
* the bottom of the server list.
|
||||
* @see <a href="https://wiki.vg/Server_List_Ping#Ping_via_LAN_.28Open_to_LAN_in_Singleplayer.29">wiki.vg</a>
|
||||
*/
|
||||
public class OpenToLAN {
|
||||
private static final InetSocketAddress PING_ADDRESS = new InetSocketAddress("224.0.2.60", 4445);
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(OpenToLAN.class);
|
||||
|
||||
private static volatile Cooldown eventCooldown;
|
||||
private static volatile DatagramSocket socket = null;
|
||||
private static volatile DatagramPacket packet = null;
|
||||
private static volatile Task task = null;
|
||||
|
||||
private OpenToLAN() { }
|
||||
|
||||
/**
|
||||
* Opens the server to LAN with the default config.
|
||||
*
|
||||
* @return {@code true} if it was open successfully, {@code false} otherwise
|
||||
*/
|
||||
public static boolean open() {
|
||||
return open(new OpenToLANConfig());
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the server to LAN.
|
||||
*
|
||||
* @param config the configuration
|
||||
* @return {@code true} if it was open successfully, {@code false} otherwise
|
||||
*/
|
||||
public static boolean open(@NotNull OpenToLANConfig config) {
|
||||
if (socket != null) {
|
||||
return false;
|
||||
} else {
|
||||
int port = config.port;
|
||||
|
||||
if (port == 0) {
|
||||
try {
|
||||
final ServerSocket socket = new ServerSocket(0);
|
||||
port = socket.getLocalPort();
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Could not find an open port!", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
socket = new DatagramSocket(port);
|
||||
} catch (SocketException e) {
|
||||
LOGGER.warn("Could not bind to the port!", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
eventCooldown = new Cooldown(config.delayBetweenEvent);
|
||||
task = MinecraftServer.getSchedulerManager().buildTask(OpenToLAN::ping)
|
||||
.repeat(config.delayBetweenPings.getValue(), config.delayBetweenPings.getTimeUnit())
|
||||
.schedule();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the server to LAN.
|
||||
*
|
||||
* @return {@code true} if it was closed, {@code false} if it was already closed
|
||||
*/
|
||||
public static boolean close() {
|
||||
if (socket == null) {
|
||||
return false;
|
||||
} else {
|
||||
task.cancel();
|
||||
socket.close();
|
||||
|
||||
task = null;
|
||||
socket = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the server is currently opened to LAN.
|
||||
*
|
||||
* @return {@code true} if it is, {@code false} otherwise
|
||||
*/
|
||||
public static boolean isOpen() {
|
||||
return socket != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the ping.
|
||||
*/
|
||||
private static void ping() {
|
||||
if (MinecraftServer.getNettyServer().getPort() != 0) {
|
||||
if (packet == null || eventCooldown.isReady(System.currentTimeMillis())) {
|
||||
final ServerListPingEvent event = new ServerListPingEvent(OPEN_TO_LAN);
|
||||
MinecraftServer.getGlobalEventHandler().callEvent(ServerListPingEvent.class, event);
|
||||
|
||||
final byte[] data = OPEN_TO_LAN.getPingResponse(event.getResponseData()).getBytes(StandardCharsets.UTF_8);
|
||||
packet = new DatagramPacket(data, data.length, PING_ADDRESS);
|
||||
|
||||
eventCooldown.refreshLastUpdate(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
socket.send(packet);
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Could not send Open to LAN packet!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package net.minestom.server.extras.lan;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import net.minestom.server.event.server.ServerListPingEvent;
|
||||
import net.minestom.server.utils.time.TimeUnit;
|
||||
import net.minestom.server.utils.time.UpdateOption;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Configuration for opening the server to LAN.
|
||||
* @see OpenToLAN#open(OpenToLANConfig)
|
||||
*/
|
||||
public class OpenToLANConfig {
|
||||
int port;
|
||||
UpdateOption delayBetweenPings, delayBetweenEvent;
|
||||
|
||||
/**
|
||||
* Creates a new config with the port set to random and the delay between pings set
|
||||
* to 1.5 seconds and the delay between event calls set to 30 seconds.
|
||||
*/
|
||||
public OpenToLANConfig() {
|
||||
this.port = 0;
|
||||
this.delayBetweenPings = new UpdateOption(1500, TimeUnit.MILLISECOND);
|
||||
this.delayBetweenEvent = new UpdateOption(30, TimeUnit.SECOND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the port used to send pings from. Use {@code 0} to pick a random free port.
|
||||
*
|
||||
* @param port the port
|
||||
* @return {@code this}, for chaining
|
||||
*/
|
||||
@Contract("_ -> this")
|
||||
public @NotNull OpenToLANConfig setPort(int port) {
|
||||
this.port = port;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the delay between outgoing pings.
|
||||
*
|
||||
* @param delay the delay
|
||||
* @return {@code this}, for chaining
|
||||
*/
|
||||
@Contract("_ -> this")
|
||||
public @NotNull OpenToLANConfig setDelayBetweenPings(@NotNull UpdateOption delay) {
|
||||
this.delayBetweenPings = Objects.requireNonNull(delay, "delay");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the delay between calls of {@link ServerListPingEvent}.
|
||||
*
|
||||
* @param delay the delay
|
||||
* @return {@code this}, for chaining
|
||||
*/
|
||||
@Contract("_ -> this")
|
||||
public @NotNull OpenToLANConfig setDelayBetweenEventCalls(@NotNull UpdateOption delay) {
|
||||
this.delayBetweenEvent = Objects.requireNonNull(delay, "delay");
|
||||
return this;
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import com.google.gson.JsonObject;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.extras.lan.OpenToLAN;
|
||||
import net.minestom.server.utils.identity.NamedAndIdentified;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@ -34,7 +35,14 @@ public enum ServerListPingVersion {
|
||||
/**
|
||||
* The client is on version 1.5 or lower and supports a description and the player count.
|
||||
*/
|
||||
LEGACY(data -> getLegacyPingResponse(data, false));
|
||||
LEGACY(data -> getLegacyPingResponse(data, false)),
|
||||
|
||||
/**
|
||||
* The ping that is sent when {@link OpenToLAN} is enabled and sending packets.
|
||||
* Only the description formatted as a legacy string is sent.
|
||||
* Ping events with this ping version are <b>not</b> cancellable.
|
||||
*/
|
||||
OPEN_TO_LAN(ServerListPingVersion::getOpenToLANPing);
|
||||
|
||||
private final Function<ResponseData, String> pingResponseCreator;
|
||||
|
||||
@ -52,10 +60,22 @@ public enum ServerListPingVersion {
|
||||
return this.pingResponseCreator.apply(responseData);
|
||||
}
|
||||
|
||||
private static final String LAN_PING_FORMAT = "[MOTD]%s[/MOTD][AD]%s[/AD]";
|
||||
private static final GsonComponentSerializer FULL_RGB = GsonComponentSerializer.gson(),
|
||||
NAMED_RGB = GsonComponentSerializer.colorDownsamplingGson();
|
||||
private static final LegacyComponentSerializer SECTION = LegacyComponentSerializer.legacySection();
|
||||
|
||||
/**
|
||||
* Creates a ping sent when the server is sending {@link OpenToLAN} packets.
|
||||
*
|
||||
* @param data the response data
|
||||
* @return the ping
|
||||
* @see OpenToLAN
|
||||
*/
|
||||
public static @NotNull String getOpenToLANPing(@NotNull ResponseData data) {
|
||||
return String.format(LAN_PING_FORMAT, SECTION.serialize(data.getDescription()), MinecraftServer.getNettyServer().getPort());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a legacy ping response for client versions below the Netty rewrite (1.6-).
|
||||
*
|
||||
|
@ -11,6 +11,8 @@ import net.kyori.adventure.text.format.TextDecoration;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.command.CommandManager;
|
||||
import net.minestom.server.event.server.ServerListPingEvent;
|
||||
import net.minestom.server.extras.lan.OpenToLAN;
|
||||
import net.minestom.server.extras.lan.OpenToLANConfig;
|
||||
import net.minestom.server.extras.optifine.OptifineSupport;
|
||||
import net.minestom.server.instance.block.BlockManager;
|
||||
import net.minestom.server.instance.block.rule.vanilla.RedstonePlacementRule;
|
||||
@ -103,6 +105,9 @@ public class Main {
|
||||
|
||||
//MojangAuth.init();
|
||||
|
||||
// useful for testing - we don't need to worry about event calls so just set this to a long time
|
||||
OpenToLAN.open(new OpenToLANConfig().setDelayBetweenEventCalls(new UpdateOption(1, TimeUnit.DAY)));
|
||||
|
||||
minecraftServer.start("0.0.0.0", 25565);
|
||||
//Runtime.getRuntime().addShutdownHook(new Thread(MinecraftServer::stopCleanly));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user