geforkt von Mirrors/Velocity
Merge branch 'dev/1.1.0' into further-native-tweaks
# Conflicts: # native/compile-linux.sh
Dieser Commit ist enthalten in:
Commit
4ca996098c
@ -5,6 +5,7 @@ plugins {
|
||||
}
|
||||
|
||||
apply from: '../gradle/checkstyle.gradle'
|
||||
apply from: '../gradle/publish.gradle'
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
|
||||
sourceSets {
|
||||
@ -30,7 +31,7 @@ dependencies {
|
||||
api "net.kyori:adventure-text-serializer-gson:${adventureVersion}"
|
||||
api "net.kyori:adventure-text-serializer-legacy:${adventureVersion}"
|
||||
api "net.kyori:adventure-text-serializer-plain:${adventureVersion}"
|
||||
api "net.kyori:adventure-text-serializer-legacy-text3:${adventureVersion}"
|
||||
api "net.kyori:adventure-text-serializer-legacy-text3:${adventurePlatformVersion}"
|
||||
|
||||
api "org.slf4j:slf4j-api:${slf4jVersion}"
|
||||
api 'com.google.inject:guice:4.2.3'
|
||||
@ -78,8 +79,8 @@ javadoc {
|
||||
'http://www.slf4j.org/apidocs/',
|
||||
'https://google.github.io/guava/releases/25.1-jre/api/docs/',
|
||||
'https://google.github.io/guice/api-docs/4.2/javadoc/',
|
||||
'https://jd.kyori.net/text-api/3.0.0/',
|
||||
'https://docs.oracle.com/javase/8/docs/api/'
|
||||
'https://docs.oracle.com/javase/8/docs/api/',
|
||||
'https://jd.adventure.kyori.net/api/4.0.0/'
|
||||
)
|
||||
|
||||
// Disable the crazy super-strict doclint tool in Java 8
|
||||
|
@ -46,9 +46,7 @@ public interface CommandManager {
|
||||
* @param command the command to register
|
||||
* @param otherAliases additional aliases
|
||||
* @throws IllegalArgumentException if one of the given aliases is already registered
|
||||
* @deprecated Prefer {@link #register(CommandMeta, Command)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
void register(String alias, Command command, String... otherAliases);
|
||||
|
||||
/**
|
||||
@ -122,4 +120,12 @@ public interface CommandManager {
|
||||
* Can be completed exceptionally if an exception is thrown during execution.
|
||||
*/
|
||||
CompletableFuture<Boolean> executeImmediatelyAsync(CommandSource source, String cmdLine);
|
||||
|
||||
/**
|
||||
* Returns whether the given alias is registered on this manager.
|
||||
*
|
||||
* @param alias the command alias to check
|
||||
* @return {@code true} if the alias is registered
|
||||
*/
|
||||
boolean hasCommand(String alias);
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
package com.velocitypowered.api.command;
|
||||
|
||||
import com.velocitypowered.api.permission.PermissionSubject;
|
||||
import com.velocitypowered.api.proxy.ProxyAudience;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.audience.MessageType;
|
||||
import net.kyori.adventure.identity.Identified;
|
||||
import net.kyori.adventure.identity.Identity;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.legacytext3.LegacyText3ComponentSerializer;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
@ -9,19 +12,21 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
/**
|
||||
* Represents something that can be used to run a {@link Command}.
|
||||
*/
|
||||
public interface CommandSource extends PermissionSubject, ProxyAudience {
|
||||
public interface CommandSource extends Audience, PermissionSubject {
|
||||
|
||||
/**
|
||||
* Sends the specified {@code component} to the invoker.
|
||||
*
|
||||
* @param component the text component to send
|
||||
* @deprecated Use {@link #sendMessage(Component)} instead
|
||||
* @deprecated Use {@link #sendMessage(Identified, Component)}
|
||||
* or {@link #sendMessage(Identity, Component)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
void sendMessage(net.kyori.text.Component component);
|
||||
|
||||
@Override
|
||||
default void sendMessage(@NonNull Component message) {
|
||||
default void sendMessage(@NonNull Identity identity, @NonNull Component message,
|
||||
@NonNull MessageType type) {
|
||||
this.sendMessage(LegacyText3ComponentSerializer.get().serialize(message));
|
||||
}
|
||||
}
|
||||
|
@ -40,9 +40,13 @@ public enum ProtocolVersion {
|
||||
MINECRAFT_1_16(735, "1.16"),
|
||||
MINECRAFT_1_16_1(736, "1.16.1"),
|
||||
MINECRAFT_1_16_2(751, "1.16.2"),
|
||||
MINECRAFT_1_16_3(753, "1.16.3");
|
||||
MINECRAFT_1_16_3(753, "1.16.3"),
|
||||
MINECRAFT_1_16_4(754, "1.16.4");
|
||||
|
||||
private static final int SNAPSHOT_BIT = 30;
|
||||
|
||||
private final int protocol;
|
||||
private final int snapshotProtocol;
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
@ -68,7 +72,13 @@ public enum ProtocolVersion {
|
||||
static {
|
||||
Map<Integer, ProtocolVersion> versions = new HashMap<>();
|
||||
for (ProtocolVersion version : values()) {
|
||||
versions.put(version.protocol, version);
|
||||
// For versions where the snapshot is compatible with the prior release version, Mojang will
|
||||
// default to that. Follow that behavior since there is precedent (all the Minecraft 1.8
|
||||
// minor releases use the same protocol version).
|
||||
versions.putIfAbsent(version.protocol, version);
|
||||
if (version.snapshotProtocol != -1) {
|
||||
versions.put(version.snapshotProtocol, version);
|
||||
}
|
||||
}
|
||||
|
||||
ID_TO_PROTOCOL_CONSTANT = ImmutableMap.copyOf(versions);
|
||||
@ -92,6 +102,16 @@ public enum ProtocolVersion {
|
||||
}
|
||||
|
||||
ProtocolVersion(int protocol, String name) {
|
||||
this(protocol, -1, name);
|
||||
}
|
||||
|
||||
ProtocolVersion(int protocol, int snapshotProtocol, String name) {
|
||||
if (snapshotProtocol != -1) {
|
||||
this.snapshotProtocol = (1 << SNAPSHOT_BIT) | snapshotProtocol;
|
||||
} else {
|
||||
this.snapshotProtocol = -1;
|
||||
}
|
||||
|
||||
this.protocol = protocol;
|
||||
this.name = name;
|
||||
}
|
||||
|
@ -14,13 +14,15 @@ import com.velocitypowered.api.util.title.Title;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import net.kyori.adventure.identity.Identified;
|
||||
import net.kyori.adventure.identity.Identity;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
/**
|
||||
* Represents a player who is connected to the proxy.
|
||||
*/
|
||||
public interface Player extends CommandSource, InboundConnection, ChannelMessageSource,
|
||||
ChannelMessageSink {
|
||||
public interface Player extends CommandSource, Identified, InboundConnection,
|
||||
ChannelMessageSource, ChannelMessageSink {
|
||||
|
||||
/**
|
||||
* Returns the player's current username.
|
||||
@ -76,7 +78,8 @@ public interface Player extends CommandSource, InboundConnection, ChannelMessage
|
||||
* Sends a chat message to the player's client.
|
||||
*
|
||||
* @param component the chat message to send
|
||||
* @deprecated Use {@link #sendMessage(net.kyori.adventure.text.Component)}
|
||||
* @deprecated Use {@link #sendMessage(Identified, Component)}
|
||||
* or {@link #sendMessage(Identity, Component)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
@ -89,8 +92,9 @@ public interface Player extends CommandSource, InboundConnection, ChannelMessage
|
||||
*
|
||||
* @param component the chat message to send
|
||||
* @param position the position for the message
|
||||
* @deprecated Use @deprecated Use {@link #sendMessage(net.kyori.adventure.text.Component)} or
|
||||
* {@link #sendActionBar(net.kyori.adventure.text.Component)}
|
||||
* @deprecated Use @deprecated Use {@link #sendMessage(Identified, Component)} or
|
||||
* {@link #sendMessage(Identity, Component)} for chat messages, or
|
||||
* {@link #sendActionBar(net.kyori.adventure.text.Component)} for action bar messages
|
||||
*/
|
||||
@Deprecated
|
||||
void sendMessage(net.kyori.text.Component component, MessagePosition position);
|
||||
|
@ -1,25 +0,0 @@
|
||||
package com.velocitypowered.api.proxy;
|
||||
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.audience.MessageType;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
/**
|
||||
* Indicates an {@link Audience} that is on the proxy. This interface contains no-op default methods
|
||||
* that are used to bridge compatibility issues with the new adventure API. This interface will go
|
||||
* away in Velocity 2.0.0.
|
||||
*
|
||||
* @deprecated Only used to handle compatibility problems, will go away in Velocity 2.0.0
|
||||
*/
|
||||
@Deprecated
|
||||
public interface ProxyAudience extends Audience {
|
||||
|
||||
@Override
|
||||
void sendMessage(@NonNull Component message);
|
||||
|
||||
@Override
|
||||
default void sendMessage(@NonNull Component message, @NonNull MessageType type) {
|
||||
sendMessage(message);
|
||||
}
|
||||
}
|
@ -18,6 +18,9 @@ import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.identity.Identified;
|
||||
import net.kyori.adventure.identity.Identity;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
/**
|
||||
@ -26,7 +29,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
public interface ProxyServer extends Audience {
|
||||
|
||||
/**
|
||||
* Shuts down the proxy, kicking players with the specified {@param reason}.
|
||||
* Shuts down the proxy, kicking players with the specified {@code reason}.
|
||||
*
|
||||
* @param reason message to kick online players with
|
||||
*/
|
||||
@ -58,7 +61,8 @@ public interface ProxyServer extends Audience {
|
||||
* Broadcasts a message to all players currently online.
|
||||
*
|
||||
* @param component the message to send
|
||||
* @deprecated Use {@link #sendMessage(net.kyori.adventure.text.Component)} instead
|
||||
* @deprecated Use {@link #sendMessage(Identified, Component)}
|
||||
* or {@link #sendMessage(Identity, Component)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
void broadcast(net.kyori.text.Component component);
|
||||
|
@ -133,8 +133,9 @@ public final class ServerPing {
|
||||
|
||||
/**
|
||||
* Returns a copy of this {@link ServerPing} instance as a builder so that it can be modified.
|
||||
* It is guaranteed that {@code ping.asBuilder().ping().equals(ping)}: that is, if no other
|
||||
* changes are made to the returned builder, the built instance will equal the original instance.
|
||||
* It is guaranteed that {@code ping.asBuilder().build().equals(ping)} is true: that is, if no
|
||||
* other changes are made to the returned builder, the built instance will equal the original
|
||||
* instance.
|
||||
*
|
||||
* @return a copy of this instance as a {@link Builder}
|
||||
*/
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Provides data structures for creating and manipulating titles.
|
||||
*
|
||||
* @deprecated Replaced with {@link net.kyori.adventure.title}
|
||||
* @deprecated Replaced with {@link net.kyori.adventure.title.Title}
|
||||
*/
|
||||
package com.velocitypowered.api.util.title;
|
11
build.gradle
11
build.gradle
@ -21,13 +21,14 @@ allprojects {
|
||||
ext {
|
||||
// dependency versions
|
||||
textVersion = '3.0.4'
|
||||
adventureVersion = '4.0.0-SNAPSHOT'
|
||||
junitVersion = '5.3.0-M1'
|
||||
slf4jVersion = '1.7.25'
|
||||
adventureVersion = '4.1.1'
|
||||
adventurePlatformVersion = '4.0.0-SNAPSHOT'
|
||||
junitVersion = '5.7.0'
|
||||
slf4jVersion = '1.7.30'
|
||||
log4jVersion = '2.13.3'
|
||||
nettyVersion = '4.1.51.Final'
|
||||
nettyVersion = '4.1.52.Final'
|
||||
guavaVersion = '25.1-jre'
|
||||
checkerFrameworkVersion = '2.7.0'
|
||||
checkerFrameworkVersion = '3.6.1'
|
||||
configurateVersion = '3.7.1'
|
||||
|
||||
getCurrentShortRevision = {
|
||||
|
16
gradle/publish.gradle
Normale Datei
16
gradle/publish.gradle
Normale Datei
@ -0,0 +1,16 @@
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
credentials {
|
||||
username System.getenv("NEXUS_USERNAME")
|
||||
password System.getenv("NEXUS_PASSWORD")
|
||||
}
|
||||
|
||||
name = 'velocity-nexus'
|
||||
def base = 'https://nexus.velocitypowered.com/repository/velocity-artifacts'
|
||||
def releasesRepoUrl = "$base-releases/"
|
||||
def snapshotsRepoUrl = "$base-snapshots/"
|
||||
url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
|
||||
}
|
||||
}
|
||||
}
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip
|
||||
|
@ -1,9 +1,11 @@
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'checkstyle'
|
||||
id 'maven-publish'
|
||||
}
|
||||
|
||||
apply from: '../gradle/checkstyle.gradle'
|
||||
apply from: '../gradle/publish.gradle'
|
||||
|
||||
dependencies {
|
||||
implementation "com.google.guava:guava:${guavaVersion}"
|
||||
@ -13,3 +15,11 @@ dependencies {
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api:${junitVersion}"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-engine:${junitVersion}"
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
from components.java
|
||||
}
|
||||
}
|
||||
}
|
@ -27,8 +27,6 @@ public class LibdeflateVelocityCompressor implements VelocityCompressor {
|
||||
public void inflate(ByteBuf source, ByteBuf destination, int uncompressedSize)
|
||||
throws DataFormatException {
|
||||
ensureNotDisposed();
|
||||
source.memoryAddress();
|
||||
destination.memoryAddress();
|
||||
|
||||
// libdeflate recommends we work with a known uncompressed size - so we work strictly within
|
||||
// those parameters. If the uncompressed size doesn't match the compressed size, then we will
|
||||
|
@ -56,16 +56,16 @@ dependencies {
|
||||
|
||||
implementation 'net.sf.jopt-simple:jopt-simple:5.0.4' // command-line options
|
||||
implementation 'net.minecrell:terminalconsoleappender:1.2.0'
|
||||
runtimeOnly 'org.jline:jline-terminal-jansi:3.12.1' // Needed for JLine
|
||||
runtimeOnly 'org.jline:jline-terminal-jansi:3.16.0' // Needed for JLine
|
||||
runtimeOnly 'com.lmax:disruptor:3.4.2' // Async loggers
|
||||
|
||||
implementation 'it.unimi.dsi:fastutil:8.2.3'
|
||||
implementation 'it.unimi.dsi:fastutil:8.4.1'
|
||||
implementation 'net.kyori:event-method-asm:4.0.0-SNAPSHOT'
|
||||
implementation 'net.kyori:adventure-nbt:4.0.0-SNAPSHOT'
|
||||
|
||||
implementation 'org.asynchttpclient:async-http-client:2.10.4'
|
||||
implementation 'org.asynchttpclient:async-http-client:2.12.1'
|
||||
|
||||
implementation 'com.spotify:completable-futures:0.3.2'
|
||||
implementation 'com.spotify:completable-futures:0.3.3'
|
||||
|
||||
implementation 'com.electronwill.night-config:toml:3.6.3'
|
||||
|
||||
|
@ -344,14 +344,14 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
player.createConnectionRequest(next.get()).connectWithIndication()
|
||||
.whenComplete((success, ex) -> {
|
||||
if (ex != null || success == null || !success) {
|
||||
player.disconnect(TextComponent.of("Your server has been changed, but we could "
|
||||
player.disconnect(Component.text("Your server has been changed, but we could "
|
||||
+ "not move you to any fallback servers."));
|
||||
}
|
||||
latch.countDown();
|
||||
});
|
||||
} else {
|
||||
latch.countDown();
|
||||
player.disconnect(TextComponent.of("Your server has been changed, but we could "
|
||||
player.disconnect(Component.text("Your server has been changed, but we could "
|
||||
+ "not move you to any fallback servers."));
|
||||
}
|
||||
}
|
||||
@ -425,8 +425,11 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
.toArray((IntFunction<CompletableFuture<Void>[]>) CompletableFuture[]::new));
|
||||
|
||||
playersTeardownFuture.get(10, TimeUnit.SECONDS);
|
||||
} catch (TimeoutException | ExecutionException e) {
|
||||
} catch (TimeoutException e) {
|
||||
timedOut = true;
|
||||
} catch (ExecutionException e) {
|
||||
timedOut = true;
|
||||
logger.error("Exception while tearing down player connections", e);
|
||||
}
|
||||
|
||||
eventManager.fireShutdownEvent();
|
||||
@ -456,8 +459,12 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
}
|
||||
};
|
||||
|
||||
if (explicitExit) {
|
||||
Thread thread = new Thread(shutdownProcess);
|
||||
thread.start();
|
||||
} else {
|
||||
shutdownProcess.run();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -466,7 +473,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
* @param explicitExit whether the user explicitly shut down the proxy
|
||||
*/
|
||||
public void shutdown(boolean explicitExit) {
|
||||
shutdown(explicitExit, TextComponent.of("Proxy shutting down."));
|
||||
shutdown(explicitExit, Component.text("Proxy shutting down."));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -520,7 +527,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
} else {
|
||||
ConnectedPlayer existing = connectionsByUuid.get(connection.getUniqueId());
|
||||
if (existing != null) {
|
||||
existing.disconnect(TranslatableComponent.of("multiplayer.disconnect.duplicate_login"));
|
||||
existing.disconnect(Component.translatable("multiplayer.disconnect.duplicate_login"));
|
||||
}
|
||||
|
||||
// We can now replace the entries as needed.
|
||||
|
@ -24,6 +24,8 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import net.kyori.adventure.identity.Identity;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
|
||||
@ -161,7 +163,7 @@ public class VelocityCommandManager implements CommandManager {
|
||||
boolean isSyntaxError = !e.getType().equals(
|
||||
CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand());
|
||||
if (isSyntaxError) {
|
||||
source.sendMessage(TextComponent.of(e.getMessage(), NamedTextColor.RED));
|
||||
source.sendMessage(Identity.nil(), Component.text(e.getMessage(), NamedTextColor.RED));
|
||||
// This is, of course, a lie, but the API will need to change...
|
||||
return true;
|
||||
} else {
|
||||
@ -227,6 +229,7 @@ public class VelocityCommandManager implements CommandManager {
|
||||
* @param alias the command alias to check
|
||||
* @return {@code true} if the alias is registered
|
||||
*/
|
||||
@Override
|
||||
public boolean hasCommand(final String alias) {
|
||||
Preconditions.checkNotNull(alias, "alias");
|
||||
return dispatcher.getRoot().getChild(alias.toLowerCase(Locale.ENGLISH)) != null;
|
||||
|
@ -17,6 +17,8 @@ import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import net.kyori.adventure.identity.Identity;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
|
||||
@ -58,10 +60,11 @@ public class GlistCommand {
|
||||
private int totalCount(final CommandContext<CommandSource> context) {
|
||||
final CommandSource source = context.getSource();
|
||||
sendTotalProxyCount(source);
|
||||
source.sendMessage(
|
||||
TextComponent.builder("To view all players on servers, use ", NamedTextColor.YELLOW)
|
||||
.append("/glist all", NamedTextColor.DARK_AQUA)
|
||||
.append(".", NamedTextColor.YELLOW)
|
||||
source.sendMessage(Identity.nil(),
|
||||
Component.text().content("To view all players on servers, use ")
|
||||
.color(NamedTextColor.YELLOW)
|
||||
.append(Component.text("/glist all", NamedTextColor.DARK_AQUA))
|
||||
.append(Component.text(".", NamedTextColor.YELLOW))
|
||||
.build());
|
||||
return 1;
|
||||
}
|
||||
@ -77,8 +80,8 @@ public class GlistCommand {
|
||||
} else {
|
||||
Optional<RegisteredServer> registeredServer = server.getServer(serverName);
|
||||
if (!registeredServer.isPresent()) {
|
||||
source.sendMessage(
|
||||
TextComponent.of("Server " + serverName + " doesn't exist.", NamedTextColor.RED));
|
||||
source.sendMessage(Identity.nil(),
|
||||
Component.text("Server " + serverName + " doesn't exist.", NamedTextColor.RED));
|
||||
return -1;
|
||||
}
|
||||
sendServerPlayers(source, registeredServer.get(), false);
|
||||
@ -87,9 +90,10 @@ public class GlistCommand {
|
||||
}
|
||||
|
||||
private void sendTotalProxyCount(CommandSource target) {
|
||||
target.sendMessage(TextComponent.builder("There are ", NamedTextColor.YELLOW)
|
||||
.append(Integer.toString(server.getAllPlayers().size()), NamedTextColor.GREEN)
|
||||
.append(" player(s) online.", NamedTextColor.YELLOW)
|
||||
target.sendMessage(Identity.nil(), Component.text()
|
||||
.content("There are ").color(NamedTextColor.YELLOW)
|
||||
.append(Component.text(server.getAllPlayers().size(), NamedTextColor.GREEN))
|
||||
.append(Component.text(" player(s) online.", NamedTextColor.YELLOW))
|
||||
.build());
|
||||
}
|
||||
|
||||
@ -99,22 +103,22 @@ public class GlistCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
TextComponent.Builder builder = TextComponent.builder()
|
||||
.append(TextComponent.of("[" + server.getServerInfo().getName() + "] ",
|
||||
TextComponent.Builder builder = Component.text()
|
||||
.append(Component.text("[" + server.getServerInfo().getName() + "] ",
|
||||
NamedTextColor.DARK_AQUA))
|
||||
.append("(" + onServer.size() + ")", NamedTextColor.GRAY)
|
||||
.append(": ")
|
||||
.append(Component.text("(" + onServer.size() + ")", NamedTextColor.GRAY))
|
||||
.append(Component.text(": "))
|
||||
.resetStyle();
|
||||
|
||||
for (int i = 0; i < onServer.size(); i++) {
|
||||
Player player = onServer.get(i);
|
||||
builder.append(player.getUsername());
|
||||
builder.append(Component.text(player.getUsername()));
|
||||
|
||||
if (i + 1 < onServer.size()) {
|
||||
builder.append(", ");
|
||||
builder.append(Component.text(", "));
|
||||
}
|
||||
}
|
||||
|
||||
target.sendMessage(builder.build());
|
||||
target.sendMessage(Identity.nil(), builder.build());
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import net.kyori.adventure.identity.Identity;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
@ -34,7 +36,7 @@ public class ServerCommand implements SimpleCommand {
|
||||
final String[] args = invocation.arguments();
|
||||
|
||||
if (!(source instanceof Player)) {
|
||||
source.sendMessage(TextComponent.of("Only players may run this command.",
|
||||
source.sendMessage(Identity.nil(), Component.text("Only players may run this command.",
|
||||
NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
@ -45,8 +47,8 @@ public class ServerCommand implements SimpleCommand {
|
||||
String serverName = args[0];
|
||||
Optional<RegisteredServer> toConnect = server.getServer(serverName);
|
||||
if (!toConnect.isPresent()) {
|
||||
player.sendMessage(
|
||||
TextComponent.of("Server " + serverName + " doesn't exist.", NamedTextColor.RED));
|
||||
player.sendMessage(Identity.nil(),
|
||||
Component.text("Server " + serverName + " doesn't exist.", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -59,45 +61,45 @@ public class ServerCommand implements SimpleCommand {
|
||||
private void outputServerInformation(Player executor) {
|
||||
String currentServer = executor.getCurrentServer().map(ServerConnection::getServerInfo)
|
||||
.map(ServerInfo::getName).orElse("<unknown>");
|
||||
executor.sendMessage(TextComponent.of("You are currently connected to " + currentServer + ".",
|
||||
NamedTextColor.YELLOW));
|
||||
executor.sendMessage(Identity.nil(), Component.text(
|
||||
"You are currently connected to " + currentServer + ".", NamedTextColor.YELLOW));
|
||||
|
||||
List<RegisteredServer> servers = BuiltinCommandUtil.sortedServerList(server);
|
||||
if (servers.size() > MAX_SERVERS_TO_LIST) {
|
||||
executor.sendMessage(TextComponent.of(
|
||||
executor.sendMessage(Identity.nil(), Component.text(
|
||||
"Too many servers to list. Tab-complete to show all servers.", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
// Assemble the list of servers as components
|
||||
TextComponent.Builder serverListBuilder = TextComponent.builder("Available servers: ")
|
||||
TextComponent.Builder serverListBuilder = Component.text().content("Available servers: ")
|
||||
.color(NamedTextColor.YELLOW);
|
||||
for (int i = 0; i < servers.size(); i++) {
|
||||
RegisteredServer rs = servers.get(i);
|
||||
serverListBuilder.append(formatServerComponent(currentServer, rs));
|
||||
if (i != servers.size() - 1) {
|
||||
serverListBuilder.append(TextComponent.of(", ", NamedTextColor.GRAY));
|
||||
serverListBuilder.append(Component.text(", ", NamedTextColor.GRAY));
|
||||
}
|
||||
}
|
||||
|
||||
executor.sendMessage(serverListBuilder.build());
|
||||
executor.sendMessage(Identity.nil(), serverListBuilder.build());
|
||||
}
|
||||
|
||||
private TextComponent formatServerComponent(String currentPlayerServer, RegisteredServer server) {
|
||||
ServerInfo serverInfo = server.getServerInfo();
|
||||
TextComponent serverTextComponent = TextComponent.of(serverInfo.getName());
|
||||
TextComponent serverTextComponent = Component.text(serverInfo.getName());
|
||||
|
||||
String playersText = server.getPlayersConnected().size() + " player(s) online";
|
||||
if (serverInfo.getName().equals(currentPlayerServer)) {
|
||||
serverTextComponent = serverTextComponent.color(NamedTextColor.GREEN)
|
||||
.hoverEvent(
|
||||
showText(TextComponent.of("Currently connected to this server\n" + playersText))
|
||||
showText(Component.text("Currently connected to this server\n" + playersText))
|
||||
);
|
||||
} else {
|
||||
serverTextComponent = serverTextComponent.color(NamedTextColor.GRAY)
|
||||
.clickEvent(ClickEvent.runCommand("/server " + serverInfo.getName()))
|
||||
.hoverEvent(
|
||||
showText(TextComponent.of("Click to connect to this server\n" + playersText))
|
||||
showText(Component.text("Click to connect to this server\n" + playersText))
|
||||
);
|
||||
}
|
||||
return serverTextComponent;
|
||||
|
@ -3,19 +3,37 @@ package com.velocitypowered.proxy.command.builtin;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.command.SimpleCommand;
|
||||
import com.velocitypowered.api.permission.Tristate;
|
||||
import com.velocitypowered.api.plugin.PluginContainer;
|
||||
import com.velocitypowered.api.plugin.PluginDescription;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.util.ProxyVersion;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.util.InformationUtils;
|
||||
|
||||
import java.net.ConnectException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import net.kyori.adventure.identity.Identity;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
@ -23,6 +41,10 @@ import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.asynchttpclient.AsyncHttpClient;
|
||||
import org.asynchttpclient.BoundRequestBuilder;
|
||||
import org.asynchttpclient.ListenableFuture;
|
||||
import org.asynchttpclient.Response;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
public class VelocityCommand implements SimpleCommand {
|
||||
@ -50,6 +72,7 @@ public class VelocityCommand implements SimpleCommand {
|
||||
.put("version", new Info(server))
|
||||
.put("plugins", new Plugins(server))
|
||||
.put("reload", new Reload(server))
|
||||
.put("dump", new Dump(server))
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -59,7 +82,7 @@ public class VelocityCommand implements SimpleCommand {
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(Collectors.joining("|"));
|
||||
String commandText = "/velocity <" + availableCommands + ">";
|
||||
source.sendMessage(TextComponent.of(commandText, NamedTextColor.RED));
|
||||
source.sendMessage(Identity.nil(), Component.text(commandText, NamedTextColor.RED));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -142,15 +165,16 @@ public class VelocityCommand implements SimpleCommand {
|
||||
public void execute(CommandSource source, String @NonNull [] args) {
|
||||
try {
|
||||
if (server.reloadConfiguration()) {
|
||||
source.sendMessage(TextComponent.of("Configuration reloaded.", NamedTextColor.GREEN));
|
||||
source.sendMessage(Identity.nil(), Component.text(
|
||||
"Configuration reloaded.", NamedTextColor.GREEN));
|
||||
} else {
|
||||
source.sendMessage(TextComponent.of(
|
||||
source.sendMessage(Identity.nil(), Component.text(
|
||||
"Unable to reload your configuration. Check the console for more details.",
|
||||
NamedTextColor.RED));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Unable to reload configuration", e);
|
||||
source.sendMessage(TextComponent.of(
|
||||
source.sendMessage(Identity.nil(), Component.text(
|
||||
"Unable to reload your configuration. Check the console for more details.",
|
||||
NamedTextColor.RED));
|
||||
}
|
||||
@ -173,39 +197,39 @@ public class VelocityCommand implements SimpleCommand {
|
||||
@Override
|
||||
public void execute(CommandSource source, String @NonNull [] args) {
|
||||
if (args.length != 0) {
|
||||
source.sendMessage(TextComponent.of("/velocity version", NamedTextColor.RED));
|
||||
source.sendMessage(Identity.nil(), Component.text("/velocity version", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
ProxyVersion version = server.getVersion();
|
||||
|
||||
TextComponent velocity = TextComponent.builder(version.getName() + " ")
|
||||
TextComponent velocity = Component.text().content(version.getName() + " ")
|
||||
.decoration(TextDecoration.BOLD, true)
|
||||
.color(NamedTextColor.DARK_AQUA)
|
||||
.append(TextComponent.of(version.getVersion()).decoration(TextDecoration.BOLD, false))
|
||||
.append(Component.text(version.getVersion()).decoration(TextDecoration.BOLD, false))
|
||||
.build();
|
||||
TextComponent copyright = TextComponent
|
||||
.of("Copyright 2018-2020 " + version.getVendor() + ". " + version.getName()
|
||||
TextComponent copyright = Component
|
||||
.text("Copyright 2018-2020 " + version.getVendor() + ". " + version.getName()
|
||||
+ " is freely licensed under the terms of the MIT License.");
|
||||
source.sendMessage(velocity);
|
||||
source.sendMessage(copyright);
|
||||
source.sendMessage(Identity.nil(), velocity);
|
||||
source.sendMessage(Identity.nil(), copyright);
|
||||
|
||||
if (version.getName().equals("Velocity")) {
|
||||
TextComponent velocityWebsite = TextComponent.builder()
|
||||
TextComponent velocityWebsite = Component.text()
|
||||
.content("Visit the ")
|
||||
.append(TextComponent.builder("Velocity website")
|
||||
.append(Component.text().content("Velocity website")
|
||||
.color(NamedTextColor.GREEN)
|
||||
.clickEvent(
|
||||
ClickEvent.openUrl("https://www.velocitypowered.com"))
|
||||
.build())
|
||||
.append(TextComponent.of(" or the "))
|
||||
.append(TextComponent.builder("Velocity GitHub")
|
||||
.append(Component.text(" or the "))
|
||||
.append(Component.text().content("Velocity GitHub")
|
||||
.color(NamedTextColor.GREEN)
|
||||
.clickEvent(ClickEvent.openUrl(
|
||||
"https://github.com/VelocityPowered/Velocity"))
|
||||
.build())
|
||||
.build();
|
||||
source.sendMessage(velocityWebsite);
|
||||
source.sendMessage(Identity.nil(), velocityWebsite);
|
||||
}
|
||||
}
|
||||
|
||||
@ -226,7 +250,7 @@ public class VelocityCommand implements SimpleCommand {
|
||||
@Override
|
||||
public void execute(CommandSource source, String @NonNull [] args) {
|
||||
if (args.length != 0) {
|
||||
source.sendMessage(TextComponent.of("/velocity plugins", NamedTextColor.RED));
|
||||
source.sendMessage(Identity.nil(), Component.text("/velocity plugins", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -234,49 +258,50 @@ public class VelocityCommand implements SimpleCommand {
|
||||
int pluginCount = plugins.size();
|
||||
|
||||
if (pluginCount == 0) {
|
||||
source.sendMessage(TextComponent.of("No plugins installed.", NamedTextColor.YELLOW));
|
||||
source.sendMessage(Identity.nil(), Component.text(
|
||||
"No plugins installed.", NamedTextColor.YELLOW));
|
||||
return;
|
||||
}
|
||||
|
||||
TextComponent.Builder output = TextComponent.builder("Plugins: ")
|
||||
TextComponent.Builder output = Component.text().content("Plugins: ")
|
||||
.color(NamedTextColor.YELLOW);
|
||||
for (int i = 0; i < pluginCount; i++) {
|
||||
PluginContainer plugin = plugins.get(i);
|
||||
output.append(componentForPlugin(plugin.getDescription()));
|
||||
if (i + 1 < pluginCount) {
|
||||
output.append(TextComponent.of(", "));
|
||||
output.append(Component.text(", "));
|
||||
}
|
||||
}
|
||||
|
||||
source.sendMessage(output.build());
|
||||
source.sendMessage(Identity.nil(), output.build());
|
||||
}
|
||||
|
||||
private TextComponent componentForPlugin(PluginDescription description) {
|
||||
String pluginInfo = description.getName().orElse(description.getId())
|
||||
+ description.getVersion().map(v -> " " + v).orElse("");
|
||||
|
||||
TextComponent.Builder hoverText = TextComponent.builder(pluginInfo);
|
||||
TextComponent.Builder hoverText = Component.text().content(pluginInfo);
|
||||
|
||||
description.getUrl().ifPresent(url -> {
|
||||
hoverText.append(TextComponent.newline());
|
||||
hoverText.append(TextComponent.of("Website: " + url));
|
||||
hoverText.append(Component.newline());
|
||||
hoverText.append(Component.text("Website: " + url));
|
||||
});
|
||||
if (!description.getAuthors().isEmpty()) {
|
||||
hoverText.append(TextComponent.newline());
|
||||
hoverText.append(Component.newline());
|
||||
if (description.getAuthors().size() == 1) {
|
||||
hoverText.append(TextComponent.of("Author: " + description.getAuthors().get(0)));
|
||||
hoverText.append(Component.text("Author: " + description.getAuthors().get(0)));
|
||||
} else {
|
||||
hoverText.append(TextComponent.of("Authors: " + Joiner.on(", ")
|
||||
hoverText.append(Component.text("Authors: " + Joiner.on(", ")
|
||||
.join(description.getAuthors())));
|
||||
}
|
||||
}
|
||||
description.getDescription().ifPresent(pdesc -> {
|
||||
hoverText.append(TextComponent.newline());
|
||||
hoverText.append(TextComponent.newline());
|
||||
hoverText.append(TextComponent.of(pdesc));
|
||||
hoverText.append(Component.newline());
|
||||
hoverText.append(Component.newline());
|
||||
hoverText.append(Component.text(pdesc));
|
||||
});
|
||||
|
||||
return TextComponent.of(description.getId(), NamedTextColor.GRAY)
|
||||
return Component.text(description.getId(), NamedTextColor.GRAY)
|
||||
.hoverEvent(HoverEvent.showText(hoverText.build()));
|
||||
}
|
||||
|
||||
@ -285,4 +310,140 @@ public class VelocityCommand implements SimpleCommand {
|
||||
return source.getPermissionValue("velocity.command.plugins") == Tristate.TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Dump implements SubCommand {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(Dump.class);
|
||||
private final ProxyServer server;
|
||||
|
||||
private Dump(ProxyServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSource source, String @NonNull [] args) {
|
||||
if (args.length != 0) {
|
||||
source.sendMessage(Identity.nil(), Component.text("/velocity dump", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
Collection<RegisteredServer> allServers = ImmutableSet.copyOf(server.getAllServers());
|
||||
JsonObject servers = new JsonObject();
|
||||
for (RegisteredServer iter : allServers) {
|
||||
servers.add(iter.getServerInfo().getName(),
|
||||
InformationUtils.collectServerInfo(iter));
|
||||
}
|
||||
JsonArray connectOrder = new JsonArray();
|
||||
List<String> attemptedConnectionOrder = ImmutableList.copyOf(
|
||||
server.getConfiguration().getAttemptConnectionOrder());
|
||||
for (int i = 0; i < attemptedConnectionOrder.size(); i++) {
|
||||
connectOrder.add(attemptedConnectionOrder.get(i));
|
||||
}
|
||||
|
||||
JsonObject proxyConfig = InformationUtils.collectProxyConfig(server.getConfiguration());
|
||||
proxyConfig.add("servers", servers);
|
||||
proxyConfig.add("connectOrder", connectOrder);
|
||||
proxyConfig.add("forcedHosts",
|
||||
InformationUtils.collectForcedHosts(server.getConfiguration()));
|
||||
|
||||
JsonObject dump = new JsonObject();
|
||||
dump.add("versionInfo", InformationUtils.collectProxyInfo(server.getVersion()));
|
||||
dump.add("platform", InformationUtils.collectEnvironmentInfo());
|
||||
dump.add("config", proxyConfig);
|
||||
dump.add("plugins", InformationUtils.collectPluginInfo(server));
|
||||
|
||||
source.sendMessage(Component.text().content("Uploading gathered information...").build());
|
||||
AsyncHttpClient httpClient = ((VelocityServer) server).getAsyncHttpClient();
|
||||
|
||||
BoundRequestBuilder request =
|
||||
httpClient.preparePost("https://dump.velocitypowered.com/documents");
|
||||
request.setHeader("Content-Type", "text/plain");
|
||||
request.addHeader("User-Agent", server.getVersion().getName() + "/"
|
||||
+ server.getVersion().getVersion());
|
||||
request.setBody(
|
||||
InformationUtils.toHumanReadableString(dump).getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
ListenableFuture<Response> future = request.execute();
|
||||
future.addListener(() -> {
|
||||
try {
|
||||
Response response = future.get();
|
||||
if (response.getStatusCode() != 200) {
|
||||
source.sendMessage(Component.text()
|
||||
.content("An error occurred while communicating with the Velocity servers. "
|
||||
+ "The servers may be temporarily unavailable or there is an issue "
|
||||
+ "with your network settings. You can find more information in the "
|
||||
+ "log or console of your Velocity server.")
|
||||
.color(NamedTextColor.RED).build());
|
||||
logger.error("Invalid status code while POST-ing Velocity dump: "
|
||||
+ response.getStatusCode());
|
||||
logger.error("Headers: \n--------------BEGIN HEADERS--------------\n"
|
||||
+ response.getHeaders().toString()
|
||||
+ "\n---------------END HEADERS---------------");
|
||||
return;
|
||||
}
|
||||
JsonObject key = InformationUtils.parseString(
|
||||
response.getResponseBody(StandardCharsets.UTF_8));
|
||||
if (!key.has("key")) {
|
||||
throw new JsonSyntaxException("Missing Dump-Url-response");
|
||||
}
|
||||
String url = "https://dump.velocitypowered.com/"
|
||||
+ key.get("key").getAsString() + ".json";
|
||||
source.sendMessage(Component.text()
|
||||
.content("Created an anonymised report containing useful information about "
|
||||
+ "this proxy. If a developer requested it, you may share the "
|
||||
+ "following link with them:")
|
||||
.append(Component.newline())
|
||||
.append(Component.text(">> " + url)
|
||||
.color(NamedTextColor.GREEN)
|
||||
.clickEvent(ClickEvent.openUrl(url)))
|
||||
.append(Component.newline())
|
||||
.append(Component.text("Note: This link is only valid for a few days")
|
||||
.color(NamedTextColor.GRAY)
|
||||
).build());
|
||||
} catch (InterruptedException e) {
|
||||
source.sendMessage(Component.text()
|
||||
.content("Could not complete the request, the command was interrupted."
|
||||
+ "Please refer to the proxy-log or console for more information.")
|
||||
.color(NamedTextColor.RED).build());
|
||||
logger.error("Failed to complete dump command, "
|
||||
+ "the executor was interrupted: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
} catch (ExecutionException e) {
|
||||
TextComponent.Builder message = Component.text()
|
||||
.content("An error occurred while attempting to upload the gathered "
|
||||
+ "information to the Velocity servers.")
|
||||
.append(Component.newline())
|
||||
.color(NamedTextColor.RED);
|
||||
if (e.getCause() instanceof UnknownHostException
|
||||
|| e.getCause() instanceof ConnectException) {
|
||||
message.append(Component.text(
|
||||
"Likely cause: Invalid system DNS settings or no internet connection"));
|
||||
}
|
||||
source.sendMessage(message
|
||||
.append(Component.newline()
|
||||
.append(Component.text(
|
||||
"Error details can be found in the proxy log / console"))
|
||||
).build());
|
||||
|
||||
logger.error("Failed to complete dump command, "
|
||||
+ "the executor encountered an Exception: " + e.getCause().getMessage());
|
||||
e.getCause().printStackTrace();
|
||||
} catch (JsonParseException e) {
|
||||
source.sendMessage(Component.text()
|
||||
.content("An error occurred on the Velocity-servers and the dump could not "
|
||||
+ "be completed. Please contact the Velocity staff about this problem. "
|
||||
+ "If you do, provide the details about this error from the Velocity "
|
||||
+ "console or server log.")
|
||||
.color(NamedTextColor.RED).build());
|
||||
logger.error("Invalid response from the Velocity servers: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(final CommandSource source, final String @NonNull [] args) {
|
||||
return source.getPermissionValue("velocity.command.plugins") == Tristate.TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import com.electronwill.nightconfig.toml.TomlFormat;
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.velocitypowered.api.proxy.config.ProxyConfig;
|
||||
import com.velocitypowered.api.util.Favicon;
|
||||
import com.velocitypowered.proxy.util.AddressUtil;
|
||||
@ -42,20 +43,20 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(VelocityConfiguration.class);
|
||||
|
||||
private String bind = "0.0.0.0:25577";
|
||||
private String motd = "&3A Velocity Server";
|
||||
private int showMaxPlayers = 500;
|
||||
private boolean onlineMode = true;
|
||||
private boolean preventClientProxyConnections = false;
|
||||
private PlayerInfoForwarding playerInfoForwardingMode = PlayerInfoForwarding.NONE;
|
||||
@Expose private String bind = "0.0.0.0:25577";
|
||||
@Expose private String motd = "&3A Velocity Server";
|
||||
@Expose private int showMaxPlayers = 500;
|
||||
@Expose private boolean onlineMode = true;
|
||||
@Expose private boolean preventClientProxyConnections = false;
|
||||
@Expose private PlayerInfoForwarding playerInfoForwardingMode = PlayerInfoForwarding.NONE;
|
||||
private byte[] forwardingSecret = generateRandomString(12).getBytes(StandardCharsets.UTF_8);
|
||||
private boolean announceForge = false;
|
||||
private boolean onlineModeKickExistingPlayers = false;
|
||||
private PingPassthroughMode pingPassthrough = PingPassthroughMode.DISABLED;
|
||||
@Expose private boolean announceForge = false;
|
||||
@Expose private boolean onlineModeKickExistingPlayers = false;
|
||||
@Expose private PingPassthroughMode pingPassthrough = PingPassthroughMode.DISABLED;
|
||||
private final Servers servers;
|
||||
private final ForcedHosts forcedHosts;
|
||||
private final Advanced advanced;
|
||||
private final Query query;
|
||||
@Expose private final Advanced advanced;
|
||||
@Expose private final Query query;
|
||||
private final Metrics metrics;
|
||||
private final Messages messages;
|
||||
private net.kyori.adventure.text.@MonotonicNonNull Component motdAsComponent;
|
||||
@ -622,18 +623,18 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
|
||||
private static class Advanced {
|
||||
|
||||
private int compressionThreshold = 256;
|
||||
private int compressionLevel = -1;
|
||||
private int loginRatelimit = 3000;
|
||||
private int connectionTimeout = 5000;
|
||||
private int readTimeout = 30000;
|
||||
private boolean proxyProtocol = false;
|
||||
private boolean tcpFastOpen = false;
|
||||
private boolean bungeePluginMessageChannel = true;
|
||||
private boolean showPingRequests = false;
|
||||
private boolean failoverOnUnexpectedServerDisconnect = true;
|
||||
private boolean announceProxyCommands = true;
|
||||
private boolean logCommandExecutions = false;
|
||||
@Expose private int compressionThreshold = 256;
|
||||
@Expose private int compressionLevel = -1;
|
||||
@Expose private int loginRatelimit = 3000;
|
||||
@Expose private int connectionTimeout = 5000;
|
||||
@Expose private int readTimeout = 30000;
|
||||
@Expose private boolean proxyProtocol = false;
|
||||
@Expose private boolean tcpFastOpen = false;
|
||||
@Expose private boolean bungeePluginMessageChannel = true;
|
||||
@Expose private boolean showPingRequests = false;
|
||||
@Expose private boolean failoverOnUnexpectedServerDisconnect = true;
|
||||
@Expose private boolean announceProxyCommands = true;
|
||||
@Expose private boolean logCommandExecutions = false;
|
||||
|
||||
private Advanced() {
|
||||
}
|
||||
@ -725,10 +726,10 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
|
||||
private static class Query {
|
||||
|
||||
private boolean queryEnabled = false;
|
||||
private int queryPort = 25577;
|
||||
private String queryMap = "Velocity";
|
||||
private boolean showPlugins = false;
|
||||
@Expose private boolean queryEnabled = false;
|
||||
@Expose private int queryPort = 25577;
|
||||
@Expose private String queryMap = "Velocity";
|
||||
@Expose private boolean showPlugins = false;
|
||||
|
||||
private Query() {
|
||||
}
|
||||
|
@ -149,7 +149,11 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
Unpooled.wrappedBuffer(copy));
|
||||
playerConnection.write(copied);
|
||||
}
|
||||
}, playerConnection.eventLoop());
|
||||
}, playerConnection.eventLoop())
|
||||
.exceptionally((ex) -> {
|
||||
logger.error("Exception while handling plugin message {}", packet, ex);
|
||||
return null;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -186,7 +190,11 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
server.getEventManager().fire(
|
||||
new PlayerAvailableCommandsEvent(serverConn.getPlayer(), rootNode))
|
||||
.thenAcceptAsync(event -> playerConnection.write(commands), playerConnection.eventLoop());
|
||||
.thenAcceptAsync(event -> playerConnection.write(commands), playerConnection.eventLoop())
|
||||
.exceptionally((ex) -> {
|
||||
logger.error("Exception while handling available commands for {}", playerConnection, ex);
|
||||
return null;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import java.util.Optional;
|
||||
import java.util.StringJoiner;
|
||||
import net.kyori.adventure.identity.Identity;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.ComponentSerializer;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
@ -155,9 +156,10 @@ class BungeeCordMessageResponder {
|
||||
|
||||
Component messageComponent = serializer.deserialize(message);
|
||||
if (target.equals("ALL")) {
|
||||
proxy.sendMessage(messageComponent);
|
||||
proxy.sendMessage(Identity.nil(), messageComponent);
|
||||
} else {
|
||||
proxy.getPlayer(target).ifPresent(player -> player.sendMessage(messageComponent));
|
||||
proxy.getPlayer(target).ifPresent(player -> player.sendMessage(Identity.nil(),
|
||||
messageComponent));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,18 +20,20 @@ import com.velocitypowered.proxy.protocol.packet.SetCompression;
|
||||
import com.velocitypowered.proxy.util.except.QuietRuntimeException;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
|
||||
public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
private static final TextComponent MODERN_IP_FORWARDING_FAILURE = TextComponent
|
||||
.of("Your server did not send a forwarding request to the proxy. Is it set up correctly?");
|
||||
private static final TextComponent MODERN_IP_FORWARDING_FAILURE = Component
|
||||
.text("Your server did not send a forwarding request to the proxy. Is it set up correctly?");
|
||||
|
||||
private final VelocityServer server;
|
||||
private final VelocityServerConnection serverConn;
|
||||
@ -57,7 +59,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
if (configuration.getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN && packet
|
||||
.getChannel().equals(VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL)) {
|
||||
ByteBuf forwardingData = createForwardingData(configuration.getForwardingSecret(),
|
||||
serverConn.getPlayer().getRemoteAddress().getHostString(),
|
||||
cleanRemoteAddress(serverConn.getPlayer().getRemoteAddress()),
|
||||
serverConn.getPlayer().getGameProfile());
|
||||
LoginPluginResponse response = new LoginPluginResponse(packet.getId(), true, forwardingData);
|
||||
mc.write(response);
|
||||
@ -126,6 +128,16 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private static String cleanRemoteAddress(InetSocketAddress address) {
|
||||
String addressString = address.getAddress().getHostAddress();
|
||||
int ipv6ScopeIdx = addressString.indexOf('%');
|
||||
if (ipv6ScopeIdx == -1) {
|
||||
return addressString;
|
||||
} else {
|
||||
return addressString.substring(0, ipv6ScopeIdx);
|
||||
}
|
||||
}
|
||||
|
||||
private static ByteBuf createForwardingData(byte[] hmacSecret, String address,
|
||||
GameProfile profile) {
|
||||
ByteBuf forwarded = Unpooled.buffer(2048);
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_13;
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_16;
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8;
|
||||
import static com.velocitypowered.proxy.protocol.util.PluginMessageUtil.constructChannelsPacket;
|
||||
|
||||
@ -43,7 +44,8 @@ import java.util.Optional;
|
||||
import java.util.Queue;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.identity.Identity;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@ -137,8 +139,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
.exceptionally(e -> {
|
||||
logger.info("Exception occurred while running command for {}",
|
||||
player.getUsername(), e);
|
||||
player.sendMessage(
|
||||
TextComponent.of("An error occurred while running this command.",
|
||||
player.sendMessage(Identity.nil(),
|
||||
Component.text("An error occurred while running this command.",
|
||||
NamedTextColor.RED));
|
||||
return null;
|
||||
});
|
||||
@ -155,7 +157,11 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
smc.write(packet);
|
||||
}
|
||||
}
|
||||
}, smc.eventLoop());
|
||||
}, smc.eventLoop())
|
||||
.exceptionally((ex) -> {
|
||||
logger.error("Exception while handling player chat for {}", player, ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -223,7 +229,12 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
Unpooled.wrappedBuffer(copy));
|
||||
backendConn.write(message);
|
||||
}
|
||||
}, backendConn.eventLoop());
|
||||
}, backendConn.eventLoop())
|
||||
.exceptionally((ex) -> {
|
||||
logger.error("Exception while handling plugin message packet for {}",
|
||||
player, ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -328,20 +339,18 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
// Most notably, by having the client accept the join game packet, we can work around the need
|
||||
// to perform entity ID rewrites, eliminating potential issues from rewriting packets and
|
||||
// improving compatibility with mods.
|
||||
player.getConnection().delayedWrite(joinGame);
|
||||
// Since 1.16 this dynamic changed:
|
||||
// We don't need to send two dimension swiches anymore!
|
||||
if (player.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_16) < 0) {
|
||||
int tempDim = joinGame.getDimension() == 0 ? -1 : 0;
|
||||
player.getConnection().delayedWrite(
|
||||
new Respawn(tempDim, joinGame.getPartialHashedSeed(), joinGame.getDifficulty(),
|
||||
joinGame.getGamemode(), joinGame.getLevelType(),
|
||||
false, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(),
|
||||
joinGame.getCurrentDimensionData()));
|
||||
|
||||
int sentOldDim = joinGame.getDimension();
|
||||
if (player.getProtocolVersion().compareTo(MINECRAFT_1_16) < 0) {
|
||||
// Before Minecraft 1.16, we could not switch to the same dimension without sending an
|
||||
// additional respawn. On older versions of Minecraft this forces the client to perform
|
||||
// garbage collection which adds additional latency.
|
||||
joinGame.setDimension(joinGame.getDimension() == 0 ? -1 : 0);
|
||||
}
|
||||
player.getConnection().delayedWrite(joinGame);
|
||||
|
||||
player.getConnection().delayedWrite(
|
||||
new Respawn(joinGame.getDimension(), joinGame.getPartialHashedSeed(),
|
||||
new Respawn(sentOldDim, joinGame.getPartialHashedSeed(),
|
||||
joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(),
|
||||
false, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(),
|
||||
joinGame.getCurrentDimensionData()));
|
||||
@ -423,7 +432,12 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
resp.getOffers().addAll(offers);
|
||||
player.getConnection().write(resp);
|
||||
}
|
||||
}, player.getConnection().eventLoop());
|
||||
}, player.getConnection().eventLoop())
|
||||
.exceptionally((ex) -> {
|
||||
logger.error("Exception while handling command tab completion for player {} executing {}",
|
||||
player, command, ex);
|
||||
return null;
|
||||
});
|
||||
return true; // Sorry, handler; we're just gonna have to lie to you here.
|
||||
}
|
||||
|
||||
@ -475,7 +489,13 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
player.getUsername(),
|
||||
command, e);
|
||||
}
|
||||
}, player.getConnection().eventLoop());
|
||||
}, player.getConnection().eventLoop())
|
||||
.exceptionally((ex) -> {
|
||||
logger.error(
|
||||
"Exception while finishing command tab completion, with request {} and response {}",
|
||||
request, response, ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private void finishRegularTabComplete(TabCompleteRequest request, TabCompleteResponse response) {
|
||||
@ -490,7 +510,13 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
response.getOffers().add(new Offer(s));
|
||||
}
|
||||
player.getConnection().write(response);
|
||||
}, player.getConnection().eventLoop());
|
||||
}, player.getConnection().eventLoop())
|
||||
.exceptionally((ex) -> {
|
||||
logger.error(
|
||||
"Exception while finishing regular tab completion, with request {} and response{}",
|
||||
request, response, ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private CompletableFuture<Void> processCommandExecuteResult(String originalCommand,
|
||||
|
@ -68,8 +68,8 @@ import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import net.kyori.adventure.audience.MessageType;
|
||||
import net.kyori.adventure.bossbar.BossBar;
|
||||
import net.kyori.adventure.identity.Identity;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
@ -90,6 +90,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(ConnectedPlayer.class);
|
||||
|
||||
private final Identity identity = new IdentityImpl();
|
||||
/**
|
||||
* The actual Minecraft connection. This is actually a wrapper object around the Netty channel.
|
||||
*/
|
||||
@ -128,6 +129,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
this.onlineMode = onlineMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Identity identity() {
|
||||
return this.identity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return profile.getName();
|
||||
@ -258,27 +264,29 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(net.kyori.adventure.text.@NonNull Component message) {
|
||||
connection.write(Chat.createClientbound(message, this.getProtocolVersion()));
|
||||
public void sendMessage(@NonNull Identity identity, @NonNull Component message) {
|
||||
connection.write(Chat.createClientbound(identity, message, this.getProtocolVersion()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NonNull Component message, @NonNull MessageType type) {
|
||||
public void sendMessage(@NonNull Identity identity, @NonNull Component message,
|
||||
@NonNull MessageType type) {
|
||||
Preconditions.checkNotNull(message, "message");
|
||||
Preconditions.checkNotNull(type, "type");
|
||||
|
||||
Chat packet = Chat.createClientbound(message, this.getProtocolVersion());
|
||||
Chat packet = Chat.createClientbound(identity, message, this.getProtocolVersion());
|
||||
packet.setType(type == MessageType.CHAT ? Chat.CHAT_TYPE : Chat.SYSTEM_TYPE);
|
||||
connection.write(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendActionBar(net.kyori.adventure.text.@NonNull Component message) {
|
||||
if (getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_11) >= 0) {
|
||||
// We can use the title packet instead.
|
||||
ProtocolVersion playerVersion = getProtocolVersion();
|
||||
if (playerVersion.compareTo(ProtocolVersion.MINECRAFT_1_11) >= 0) {
|
||||
// Use the title packet instead.
|
||||
TitlePacket pkt = new TitlePacket();
|
||||
pkt.setAction(TitlePacket.SET_ACTION_BAR);
|
||||
pkt.setComponent(ProtocolUtils.getJsonChatSerializer(this.getProtocolVersion())
|
||||
pkt.setComponent(ProtocolUtils.getJsonChatSerializer(playerVersion)
|
||||
.serialize(message));
|
||||
connection.write(pkt);
|
||||
} else {
|
||||
@ -288,7 +296,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
object.addProperty("text", LegacyComponentSerializer.legacySection().serialize(message));
|
||||
Chat chat = new Chat();
|
||||
chat.setMessage(object.toString());
|
||||
chat.setType((byte) 1);
|
||||
chat.setType(Chat.GAME_INFO_TYPE);
|
||||
connection.write(chat);
|
||||
}
|
||||
}
|
||||
@ -311,9 +319,9 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
TitlePacket timesPkt = TitlePacket.timesForProtocolVersion(this.getProtocolVersion());
|
||||
net.kyori.adventure.title.Title.Times times = title.times();
|
||||
if (times != null) {
|
||||
timesPkt.setFadeIn((int) DurationUtils.convertDurationToTicks(times.fadeIn()));
|
||||
timesPkt.setStay((int) DurationUtils.convertDurationToTicks(times.stay()));
|
||||
timesPkt.setFadeOut((int) DurationUtils.convertDurationToTicks(times.fadeOut()));
|
||||
timesPkt.setFadeIn((int) DurationUtils.toTicks(times.fadeIn()));
|
||||
timesPkt.setStay((int) DurationUtils.toTicks(times.stay()));
|
||||
timesPkt.setFadeOut((int) DurationUtils.toTicks(times.fadeOut()));
|
||||
}
|
||||
connection.delayedWrite(timesPkt);
|
||||
|
||||
@ -504,7 +512,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
userMessage = "Unable to connect to " + server.getServerInfo().getName() + ". Try again "
|
||||
+ "later.";
|
||||
}
|
||||
handleConnectionException(server, null, TextComponent.of(userMessage,
|
||||
handleConnectionException(server, null, Component.text(userMessage,
|
||||
NamedTextColor.RED), safe);
|
||||
}
|
||||
|
||||
@ -527,7 +535,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
if (connectedServer != null && connectedServer.getServerInfo().equals(server.getServerInfo())) {
|
||||
logger.error("{}: kicked from server {}: {}", this, server.getServerInfo().getName(),
|
||||
plainTextReason);
|
||||
handleConnectionException(server, disconnectReason, TextComponent.builder()
|
||||
handleConnectionException(server, disconnectReason, Component.text()
|
||||
.append(messages.getKickPrefix(server.getServerInfo().getName()))
|
||||
.color(NamedTextColor.RED)
|
||||
.append(disconnectReason)
|
||||
@ -535,7 +543,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
} else {
|
||||
logger.error("{}: disconnected while connecting to {}: {}", this,
|
||||
server.getServerInfo().getName(), plainTextReason);
|
||||
handleConnectionException(server, disconnectReason, TextComponent.builder()
|
||||
handleConnectionException(server, disconnectReason, Component.text()
|
||||
.append(messages.getDisconnectPrefix(server.getServerInfo().getName()))
|
||||
.color(NamedTextColor.RED)
|
||||
.append(disconnectReason)
|
||||
@ -623,7 +631,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
getProtocolVersion()), ((Impl) status).isSafe());
|
||||
break;
|
||||
case SUCCESS:
|
||||
sendMessage(server.getConfiguration().getMessages()
|
||||
sendMessage(Identity.nil(), server.getConfiguration().getMessages()
|
||||
.getMovedToNewServerPrefix().append(friendlyReason));
|
||||
break;
|
||||
default:
|
||||
@ -634,7 +642,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
} else if (event.getResult() instanceof Notify) {
|
||||
Notify res = (Notify) event.getResult();
|
||||
if (event.kickedDuringServerConnect() && previouslyConnected) {
|
||||
sendMessage(res.getMessageComponent());
|
||||
sendMessage(Identity.nil(), res.getMessageComponent());
|
||||
} else {
|
||||
disconnect(res.getMessageComponent());
|
||||
}
|
||||
@ -749,7 +757,13 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
}
|
||||
|
||||
DisconnectEvent event = new DisconnectEvent(this, status);
|
||||
server.getEventManager().fire(event).thenRun(() -> this.teardownFuture.complete(null));
|
||||
server.getEventManager().fire(event).whenComplete((val, ex) -> {
|
||||
if (ex == null) {
|
||||
this.teardownFuture.complete(null);
|
||||
} else {
|
||||
this.teardownFuture.completeExceptionally(ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> getTeardownFuture() {
|
||||
@ -871,6 +885,13 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
return minecraftOrFmlMessage || knownChannels.contains(message.getChannel());
|
||||
}
|
||||
|
||||
private class IdentityImpl implements Identity {
|
||||
@Override
|
||||
public @NonNull UUID uuid() {
|
||||
return ConnectedPlayer.this.getUniqueId();
|
||||
}
|
||||
}
|
||||
|
||||
private class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder {
|
||||
|
||||
private final RegisteredServer toConnect;
|
||||
@ -967,10 +988,10 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
|
||||
switch (status.getStatus()) {
|
||||
case ALREADY_CONNECTED:
|
||||
sendMessage(ConnectionMessages.ALREADY_CONNECTED);
|
||||
sendMessage(Identity.nil(), ConnectionMessages.ALREADY_CONNECTED);
|
||||
break;
|
||||
case CONNECTION_IN_PROGRESS:
|
||||
sendMessage(ConnectionMessages.IN_PROGRESS);
|
||||
sendMessage(Identity.nil(), ConnectionMessages.IN_PROGRESS);
|
||||
break;
|
||||
case CONNECTION_CANCELLED:
|
||||
// Ignored; the plugin probably already handled this.
|
||||
|
@ -22,6 +22,7 @@ import io.netty.buffer.ByteBuf;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Optional;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
@ -54,7 +55,7 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
@Override
|
||||
public boolean handle(LegacyHandshake packet) {
|
||||
connection.closeWith(LegacyDisconnect
|
||||
.from(TextComponent.of("Your client is old, please upgrade!", NamedTextColor.RED)));
|
||||
.from(Component.text("Your client is old, please upgrade!", NamedTextColor.RED)));
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -100,13 +101,13 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
private void handleLogin(Handshake handshake, InitialInboundConnection ic) {
|
||||
if (!ProtocolVersion.isSupported(handshake.getProtocolVersion())) {
|
||||
ic.disconnectQuietly(TranslatableComponent.of("multiplayer.disconnect.outdated_client"));
|
||||
ic.disconnectQuietly(Component.translatable("multiplayer.disconnect.outdated_client"));
|
||||
return;
|
||||
}
|
||||
|
||||
InetAddress address = ((InetSocketAddress) connection.getRemoteAddress()).getAddress();
|
||||
if (!server.getIpAttemptLimiter().attempt(address)) {
|
||||
ic.disconnectQuietly(TextComponent.of("You are logging in too fast, try again later."));
|
||||
ic.disconnectQuietly(Component.text("You are logging in too fast, try again later."));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -116,7 +117,7 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
// and lower, otherwise IP information will never get forwarded.
|
||||
if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN
|
||||
&& handshake.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) {
|
||||
ic.disconnectQuietly(TextComponent.of("This server is only compatible with 1.13 and above."));
|
||||
ic.disconnectQuietly(Component.text("This server is only compatible with 1.13 and above."));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -183,7 +183,11 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
} else {
|
||||
initializePlayer(GameProfile.forOfflinePlayer(login.getUsername()), false);
|
||||
}
|
||||
}, mcConnection.eventLoop());
|
||||
}, mcConnection.eventLoop())
|
||||
.exceptionally((ex) -> {
|
||||
logger.error("Exception in pre-login stage", ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private EncryptionRequest generateEncryptionRequest() {
|
||||
@ -202,6 +206,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
server.getConfiguration().getPlayerInfoForwardingMode());
|
||||
GameProfileRequestEvent profileRequestEvent = new GameProfileRequestEvent(inbound, profile,
|
||||
onlineMode);
|
||||
final GameProfile finalProfile = profile;
|
||||
|
||||
server.getEventManager().fire(profileRequestEvent).thenCompose(profileEvent -> {
|
||||
if (mcConnection.isClosed()) {
|
||||
@ -229,6 +234,9 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
completeLoginProtocolPhaseAndInitialize(player);
|
||||
}
|
||||
}, mcConnection.eventLoop());
|
||||
}).exceptionally((ex) -> {
|
||||
logger.error("Exception during connection of {}", finalProfile, ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@ -274,7 +282,11 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
server.getEventManager().fire(new PostLoginEvent(player))
|
||||
.thenRun(() -> connectToInitialServer(player));
|
||||
}
|
||||
}, mcConnection.eventLoop());
|
||||
}, mcConnection.eventLoop())
|
||||
.exceptionally((ex) -> {
|
||||
logger.error("Exception while completing login initialisation phase for {}", player, ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private void connectToInitialServer(ConnectedPlayer player) {
|
||||
@ -291,7 +303,11 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
return;
|
||||
}
|
||||
player.createConnectionRequest(toTry.get()).fireAndForget();
|
||||
}, mcConnection.eventLoop());
|
||||
}, mcConnection.eventLoop())
|
||||
.exceptionally((ex) -> {
|
||||
logger.error("Exception while connecting {} to initial server", player, ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -163,7 +163,11 @@ public class StatusSessionHandler implements MinecraftSessionHandler {
|
||||
.thenCompose(ping -> server.getEventManager().fire(new ProxyPingEvent(inbound, ping)))
|
||||
.thenAcceptAsync(event -> connection.closeWith(
|
||||
LegacyDisconnect.fromServerPing(event.getPing(), packet.getVersion())),
|
||||
connection.eventLoop());
|
||||
connection.eventLoop())
|
||||
.exceptionally((ex) -> {
|
||||
logger.error("Exception while handling legacy ping {}", packet, ex);
|
||||
return null;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -189,7 +193,11 @@ public class StatusSessionHandler implements MinecraftSessionHandler {
|
||||
.toJson(event.getPing(), json);
|
||||
connection.write(new StatusResponse(json));
|
||||
},
|
||||
connection.eventLoop());
|
||||
connection.eventLoop())
|
||||
.exceptionally((ex) -> {
|
||||
logger.error("Exception while handling status request {}", packet, ex);
|
||||
return null;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1,16 +1,17 @@
|
||||
package com.velocitypowered.proxy.connection.util;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
|
||||
public class ConnectionMessages {
|
||||
|
||||
public static final TextComponent ALREADY_CONNECTED = TextComponent
|
||||
.of("You are already connected to this server!", NamedTextColor.RED);
|
||||
public static final TextComponent IN_PROGRESS = TextComponent
|
||||
.of("You are already connecting to a server!", NamedTextColor.RED);
|
||||
public static final TextComponent INTERNAL_SERVER_CONNECTION_ERROR = TextComponent
|
||||
.of("An internal server connection error occurred.", NamedTextColor.RED);
|
||||
public static final TextComponent ALREADY_CONNECTED = Component
|
||||
.text("You are already connected to this server!", NamedTextColor.RED);
|
||||
public static final TextComponent IN_PROGRESS = Component
|
||||
.text("You are already connecting to a server!", NamedTextColor.RED);
|
||||
public static final TextComponent INTERNAL_SERVER_CONNECTION_ERROR = Component
|
||||
.text("An internal server connection error occurred.", NamedTextColor.RED);
|
||||
|
||||
private ConnectionMessages() {
|
||||
throw new AssertionError();
|
||||
|
@ -8,6 +8,8 @@ import com.velocitypowered.api.permission.Tristate;
|
||||
import com.velocitypowered.api.proxy.ConsoleCommandSource;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import java.util.List;
|
||||
import net.kyori.adventure.identity.Identity;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.format.TextColor;
|
||||
import net.minecrell.terminalconsole.SimpleTerminalConsole;
|
||||
@ -38,7 +40,7 @@ public final class VelocityConsole extends SimpleTerminalConsole implements Cons
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(net.kyori.adventure.text.@NonNull Component message) {
|
||||
public void sendMessage(@NonNull Identity identity, @NonNull Component message) {
|
||||
logger.info(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection()
|
||||
.serialize(message));
|
||||
}
|
||||
|
@ -115,12 +115,10 @@ public class VelocityEventManager implements EventManager {
|
||||
return CompletableFuture.completedFuture(event);
|
||||
}
|
||||
|
||||
CompletableFuture<E> eventFuture = new CompletableFuture<>();
|
||||
service.execute(() -> {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
fireEvent(event);
|
||||
eventFuture.complete(event);
|
||||
});
|
||||
return eventFuture;
|
||||
return event;
|
||||
}, service);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -41,7 +41,7 @@ public class VelocityPluginManager implements PluginManager {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(VelocityPluginManager.class);
|
||||
|
||||
private final Map<String, PluginContainer> plugins = new HashMap<>();
|
||||
private final Map<String, PluginContainer> plugins = new LinkedHashMap<>();
|
||||
private final Map<Object, PluginContainer> pluginInstances = new IdentityHashMap<>();
|
||||
private final VelocityServer server;
|
||||
|
||||
|
@ -271,10 +271,6 @@ public enum ProtocolUtils {
|
||||
* @param stringArray the array to write
|
||||
*/
|
||||
public static void writeStringArray(ByteBuf buf, String[] stringArray) {
|
||||
if (stringArray == null) {
|
||||
writeVarInt(buf, 0);
|
||||
return;
|
||||
}
|
||||
writeVarInt(buf, stringArray.length);
|
||||
for (String s : stringArray) {
|
||||
writeString(buf, s);
|
||||
@ -292,7 +288,7 @@ public enum ProtocolUtils {
|
||||
writeString(buf, property.getName());
|
||||
writeString(buf, property.getValue());
|
||||
String signature = property.getSignature();
|
||||
if (signature != null) {
|
||||
if (signature != null && !signature.isEmpty()) {
|
||||
buf.writeBoolean(true);
|
||||
writeString(buf, signature);
|
||||
} else {
|
||||
|
@ -29,6 +29,7 @@ import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainComponentSerializer;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
|
||||
public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket> {
|
||||
|
||||
@ -128,7 +129,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
|
||||
|
||||
// Build initial query response
|
||||
QueryResponse response = createInitialResponse();
|
||||
boolean isBasic = queryMessage.isReadable();
|
||||
boolean isBasic = !queryMessage.isReadable();
|
||||
|
||||
// Call event and write response
|
||||
server.getEventManager()
|
||||
@ -162,7 +163,12 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
|
||||
// Send the response
|
||||
DatagramPacket responsePacket = new DatagramPacket(queryResponse, msg.sender());
|
||||
ctx.writeAndFlush(responsePacket, ctx.voidPromise());
|
||||
}, ctx.channel().eventLoop());
|
||||
}, ctx.channel().eventLoop())
|
||||
.exceptionally((ex) -> {
|
||||
LogManager.getLogger(getClass()).error(
|
||||
"Exception while writing GS4 response for query from {}", senderAddress, ex);
|
||||
return null;
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
@ -6,6 +6,7 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.kyori.adventure.identity.Identity;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
@ -14,6 +15,7 @@ public class Chat implements MinecraftPacket {
|
||||
|
||||
public static final byte CHAT_TYPE = (byte) 0;
|
||||
public static final byte SYSTEM_TYPE = (byte) 1;
|
||||
public static final byte GAME_INFO_TYPE = (byte) 2;
|
||||
|
||||
public static final int MAX_SERVERBOUND_MESSAGE_LENGTH = 256;
|
||||
public static final UUID EMPTY_SENDER = new UUID(0, 0);
|
||||
@ -109,9 +111,9 @@ public class Chat implements MinecraftPacket {
|
||||
.serialize(component), type, sender);
|
||||
}
|
||||
|
||||
public static Chat createClientbound(net.kyori.adventure.text.Component component,
|
||||
ProtocolVersion version) {
|
||||
return createClientbound(component, CHAT_TYPE, EMPTY_SENDER, version);
|
||||
public static Chat createClientbound(Identity identity,
|
||||
net.kyori.adventure.text.Component component, ProtocolVersion version) {
|
||||
return createClientbound(component, CHAT_TYPE, identity.uuid(), version);
|
||||
}
|
||||
|
||||
public static Chat createClientbound(net.kyori.adventure.text.Component component, byte type,
|
||||
|
@ -10,7 +10,7 @@ import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* A {@link DataOutput} equivalent to {@link ByteBufDataInput}.
|
||||
* A {@link ByteArrayDataOutput} equivalent to {@link ByteBufDataInput}.
|
||||
*/
|
||||
public class ByteBufDataOutput extends OutputStream implements ByteArrayDataOutput {
|
||||
|
||||
|
@ -31,7 +31,7 @@ public class VelocityLegacyHoverEventSerializer implements LegacyHoverEventSeria
|
||||
}
|
||||
|
||||
private static Key legacyIdToFakeKey(byte id) {
|
||||
return Key.of("velocity", "legacy_hover/id_" + id);
|
||||
return Key.key("velocity", "legacy_hover/id_" + id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -45,7 +45,7 @@ public class VelocityLegacyHoverEventSerializer implements LegacyHoverEventSeria
|
||||
if (idIfString.isEmpty()) {
|
||||
key = legacyIdToFakeKey(item.getByte("id"));
|
||||
} else {
|
||||
key = Key.of(idIfString);
|
||||
key = Key.key(idIfString);
|
||||
}
|
||||
|
||||
byte count = item.getByte("Count", (byte) 1);
|
||||
@ -62,10 +62,10 @@ public class VelocityLegacyHoverEventSerializer implements LegacyHoverEventSeria
|
||||
try {
|
||||
name = componentDecoder.decode(item.getString("name"));
|
||||
} catch (Exception e) {
|
||||
name = TextComponent.of(item.getString("name"));
|
||||
name = Component.text(item.getString("name"));
|
||||
}
|
||||
|
||||
return ShowEntity.of(Key.of(item.getString("type")),
|
||||
return ShowEntity.of(Key.key(item.getString("type")),
|
||||
UUID.fromString(item.getString("id")),
|
||||
name);
|
||||
}
|
||||
@ -89,7 +89,7 @@ public class VelocityLegacyHoverEventSerializer implements LegacyHoverEventSeria
|
||||
builder.put("tag", TagStringIO.get().asCompound(nbt.string()));
|
||||
}
|
||||
|
||||
return TextComponent.of(TagStringIO.get().asString(builder.build()));
|
||||
return Component.text(TagStringIO.get().asString(builder.build()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -102,6 +102,6 @@ public class VelocityLegacyHoverEventSerializer implements LegacyHoverEventSeria
|
||||
if (name != null) {
|
||||
tag.putString("name", componentEncoder.encode(name));
|
||||
}
|
||||
return TextComponent.of(TagStringIO.get().asString(tag.build()));
|
||||
return Component.text(TagStringIO.get().asString(tag.build()));
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,8 @@
|
||||
package com.velocitypowered.proxy.util;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.mojang.brigadier.Command;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.builder.ArgumentBuilder;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
@ -13,7 +11,6 @@ import com.mojang.brigadier.tree.CommandNode;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import java.util.Locale;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Provides utilities for working with Brigadier commands.
|
||||
|
@ -18,7 +18,7 @@ public final class DurationUtils {
|
||||
* @param duration the duration to convert into Minecraft ticks
|
||||
* @return the duration represented as the number of Minecraft ticks
|
||||
*/
|
||||
public static long convertDurationToTicks(Duration duration) {
|
||||
public static long toTicks(Duration duration) {
|
||||
return duration.toMillis() / ONE_TICK_IN_MILLISECONDS;
|
||||
}
|
||||
}
|
||||
|
240
proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java
Normale Datei
240
proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java
Normale Datei
@ -0,0 +1,240 @@
|
||||
package com.velocitypowered.proxy.util;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.velocitypowered.api.plugin.PluginContainer;
|
||||
import com.velocitypowered.api.plugin.PluginDescription;
|
||||
import com.velocitypowered.api.plugin.meta.PluginDependency;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.proxy.config.ProxyConfig;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.util.ProxyVersion;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public enum InformationUtils {
|
||||
;
|
||||
|
||||
/**
|
||||
* Retrieves a {@link JsonArray} containing basic information about all
|
||||
* running plugins on the {@link ProxyServer} instance.
|
||||
*
|
||||
* @param proxy the proxy instance to retrieve from
|
||||
* @return {@link JsonArray} containing zero or more {@link JsonObject}
|
||||
*/
|
||||
public static JsonArray collectPluginInfo(ProxyServer proxy) {
|
||||
List<PluginContainer> allPlugins = ImmutableList.copyOf(
|
||||
proxy.getPluginManager().getPlugins());
|
||||
JsonArray plugins = new JsonArray();
|
||||
|
||||
for (PluginContainer plugin : allPlugins) {
|
||||
PluginDescription desc = plugin.getDescription();
|
||||
JsonObject current = new JsonObject();
|
||||
current.addProperty("id", desc.getId());
|
||||
if (desc.getName().isPresent()) {
|
||||
current.addProperty("name", desc.getName().get());
|
||||
}
|
||||
if (desc.getVersion().isPresent()) {
|
||||
current.addProperty("version", desc.getVersion().get());
|
||||
}
|
||||
if (!desc.getAuthors().isEmpty()) {
|
||||
JsonArray authorsArray = new JsonArray();
|
||||
for (String author : desc.getAuthors()) {
|
||||
authorsArray.add(author);
|
||||
}
|
||||
current.add("authors", authorsArray);
|
||||
}
|
||||
if (desc.getDescription().isPresent()) {
|
||||
current.addProperty("description", desc.getDescription().get());
|
||||
}
|
||||
if (desc.getUrl().isPresent()) {
|
||||
current.addProperty("url", desc.getUrl().get());
|
||||
}
|
||||
if (!desc.getDependencies().isEmpty()) {
|
||||
JsonArray dependencies = new JsonArray();
|
||||
for (PluginDependency dependency : desc.getDependencies()) {
|
||||
dependencies.add(dependency.getId());
|
||||
}
|
||||
current.add("dependencies", dependencies);
|
||||
}
|
||||
plugins.add(current);
|
||||
}
|
||||
return plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link JsonObject} containing information about the
|
||||
* current environment the project is run under.
|
||||
*
|
||||
* @return {@link JsonObject} containing environment info
|
||||
*/
|
||||
public static JsonObject collectEnvironmentInfo() {
|
||||
JsonObject envInfo = new JsonObject();
|
||||
envInfo.addProperty("operatingSystemType", System.getProperty("os.name"));
|
||||
envInfo.addProperty("operatingSystemVersion", System.getProperty("os.version"));
|
||||
envInfo.addProperty("operatingSystemArchitecture", System.getProperty("os.arch"));
|
||||
envInfo.addProperty("javaVersion", System.getProperty("java.version"));
|
||||
envInfo.addProperty("javaVendor", System.getProperty("java.vendor"));
|
||||
return envInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link JsonObject} containing information about the
|
||||
* forced hosts of the {@link ProxyConfig} instance.
|
||||
*
|
||||
* @return {@link JsonArray} containing forced hosts
|
||||
*/
|
||||
public static JsonObject collectForcedHosts(ProxyConfig config) {
|
||||
JsonObject forcedHosts = new JsonObject();
|
||||
Map<String, List<String>> allForcedHosts = ImmutableMap.copyOf(
|
||||
config.getForcedHosts());
|
||||
for (Map.Entry<String, List<String>> entry : allForcedHosts.entrySet()) {
|
||||
JsonArray host = new JsonArray();
|
||||
for (int i = 0; i < entry.getValue().size(); i++) {
|
||||
host.add(entry.getValue().get(i));
|
||||
}
|
||||
forcedHosts.add(entry.getKey(), host);
|
||||
}
|
||||
return forcedHosts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Anonymises or redacts a given {@link InetAddress}
|
||||
* public address bits.
|
||||
*
|
||||
* @param address The address to redact
|
||||
* @return {@link String} address with public parts redacted
|
||||
*/
|
||||
public static String anonymizeInetAddress(InetAddress address) {
|
||||
if (address instanceof Inet4Address) {
|
||||
Inet4Address v4 = (Inet4Address) address;
|
||||
if (v4.isAnyLocalAddress() || v4.isLoopbackAddress()
|
||||
|| v4.isLinkLocalAddress()
|
||||
|| v4.isSiteLocalAddress()) {
|
||||
return address.getHostAddress();
|
||||
} else {
|
||||
byte[] addr = v4.getAddress();
|
||||
return (addr[0] & 0xff) + "." + (addr[1] & 0xff) + ".XXX.XXX";
|
||||
}
|
||||
} else if (address instanceof Inet6Address) {
|
||||
Inet6Address v6 = (Inet6Address) address;
|
||||
if (v6.isAnyLocalAddress() || v6.isLoopbackAddress()
|
||||
|| v6.isSiteLocalAddress()
|
||||
|| v6.isSiteLocalAddress()) {
|
||||
return address.getHostAddress();
|
||||
} else {
|
||||
String[] bits = v6.getHostAddress().split(":");
|
||||
String ret = "";
|
||||
boolean flag = false;
|
||||
for (int iter = 0; iter < bits.length; iter++) {
|
||||
if (flag) {
|
||||
ret += ":X";
|
||||
continue;
|
||||
}
|
||||
if (!bits[iter].equals("0")) {
|
||||
if (iter == 0) {
|
||||
ret = bits[iter];
|
||||
} else {
|
||||
ret = "::" + bits[iter];
|
||||
}
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
return address.getHostAddress();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link JsonObject} containing most relevant
|
||||
* information of the {@link RegisteredServer} for diagnosis.
|
||||
*
|
||||
* @param server the server to evaluate
|
||||
* @return {@link JsonObject} containing server and diagnostic info
|
||||
*/
|
||||
public static JsonObject collectServerInfo(RegisteredServer server) {
|
||||
JsonObject info = new JsonObject();
|
||||
info.addProperty("currentPlayers", server.getPlayersConnected().size());
|
||||
InetSocketAddress iaddr = server.getServerInfo().getAddress();
|
||||
if (iaddr.isUnresolved()) {
|
||||
// Greetings form Netty 4aa10db9
|
||||
info.addProperty("host", iaddr.getHostString());
|
||||
} else {
|
||||
info.addProperty("host", anonymizeInetAddress(iaddr.getAddress()));
|
||||
}
|
||||
info.addProperty("port", iaddr.getPort());
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link JsonObject} containing information about the
|
||||
* current environment the project is run under.
|
||||
*
|
||||
* @param version the proxy instance to retrieve from
|
||||
* @return {@link JsonObject} containing environment info
|
||||
*/
|
||||
public static JsonObject collectProxyInfo(ProxyVersion version) {
|
||||
return (JsonObject) serializeObject(version, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link JsonObject} containing most relevant
|
||||
* information of the {@link ProxyConfig} for diagnosis.
|
||||
*
|
||||
* @param config the config instance to retrieve from
|
||||
* @return {@link JsonObject} containing select config values
|
||||
*/
|
||||
public static JsonObject collectProxyConfig(ProxyConfig config) {
|
||||
return (JsonObject) serializeObject(config, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a human-readable String from a {@link JsonElement}.
|
||||
*
|
||||
* @param json the {@link JsonElement} object
|
||||
* @return the human-readable String
|
||||
*/
|
||||
public static String toHumanReadableString(JsonElement json) {
|
||||
return GSON_WITHOUT_EXCLUDES.toJson(json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link JsonObject} from a String.
|
||||
*
|
||||
* @param toParse the String to parse
|
||||
* @return {@link JsonObject} object
|
||||
*/
|
||||
public static JsonObject parseString(String toParse) {
|
||||
return GSON_WITHOUT_EXCLUDES.fromJson(toParse, JsonObject.class);
|
||||
}
|
||||
|
||||
private static JsonElement serializeObject(Object toSerialize, boolean withExcludes) {
|
||||
return JsonParser.parseString(
|
||||
withExcludes ? GSON_WITH_EXCLUDES.toJson(toSerialize) :
|
||||
GSON_WITHOUT_EXCLUDES.toJson(toSerialize));
|
||||
}
|
||||
|
||||
private static final Gson GSON_WITH_EXCLUDES = new GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.excludeFieldsWithoutExposeAnnotation()
|
||||
.create();
|
||||
|
||||
private static final Gson GSON_WITHOUT_EXCLUDES = new GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.create();
|
||||
|
||||
|
||||
}
|
@ -21,8 +21,7 @@ public class VelocityChannelRegistrar implements ChannelRegistrar {
|
||||
public void register(ChannelIdentifier... identifiers) {
|
||||
for (ChannelIdentifier identifier : identifiers) {
|
||||
Preconditions.checkArgument(identifier instanceof LegacyChannelIdentifier
|
||||
|| identifier instanceof MinecraftChannelIdentifier,
|
||||
"identifier is unknown");
|
||||
|| identifier instanceof MinecraftChannelIdentifier, "identifier is unknown");
|
||||
}
|
||||
|
||||
for (ChannelIdentifier identifier : identifiers) {
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren