diff --git a/build.gradle.kts b/build.gradle.kts index cc27316..c7abd94 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,9 +19,8 @@ dependencies { compileOnly("com.velocitypowered:velocity-api") compileOnly("com.velocitypowered:velocity-proxy") annotationProcessor("com.velocitypowered:velocity-api") - implementation("com.electronwill.night-config:toml:3.6.6") + compileOnly("com.electronwill.night-config:toml:3.6.6") implementation("org.bstats:bstats-velocity:3.0.0") - implementation("org.apache.commons:commons-collections4:4.4") compileOnly("io.netty:netty-buffer:4.1.86.Final") compileOnly("io.netty:netty-transport:4.1.86.Final") compileOnly("io.netty:netty-codec:4.1.86.Final") diff --git a/src/main/java/org/adde0109/ambassador/Ambassador.java b/src/main/java/org/adde0109/ambassador/Ambassador.java index 5060a7e..67aeb59 100644 --- a/src/main/java/org/adde0109/ambassador/Ambassador.java +++ b/src/main/java/org/adde0109/ambassador/Ambassador.java @@ -4,6 +4,7 @@ import com.google.inject.Inject; import com.velocitypowered.api.event.PostOrder; import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; +import com.velocitypowered.api.event.proxy.ProxyReloadEvent; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.plugin.Plugin; import com.velocitypowered.api.plugin.annotation.DataDirectory; @@ -11,12 +12,13 @@ import com.velocitypowered.api.proxy.ProxyServer; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.concurrent.Callable; +import java.nio.file.Files; +import java.util.HashMap; +import java.util.Map; +import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.proxy.VelocityServer; -import com.velocitypowered.proxy.network.BackendChannelInitializer; import com.velocitypowered.proxy.network.ConnectionManager; -import com.velocitypowered.proxy.network.ServerChannelInitializer; import com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier; import com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentPropertyRegistry; import com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentPropertySerializer; @@ -28,11 +30,11 @@ import org.adde0109.ambassador.velocity.VelocityEventHandler; import org.adde0109.ambassador.velocity.protocol.EnumArgumentProperty; import org.adde0109.ambassador.velocity.protocol.EnumArgumentPropertySerializer; import org.adde0109.ambassador.velocity.protocol.ModIdArgumentProperty; -import org.bstats.charts.SingleLineChart; import org.bstats.velocity.Metrics; import org.slf4j.Logger; import java.nio.file.Path; +import java.util.concurrent.TimeUnit; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19; import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.mapSet; @@ -45,6 +47,10 @@ public class Ambassador { private final Metrics.Factory metricsFactory; private final Path dataDirectory; + public AmbassadorConfig config; + + private static final MapWithExpiration TEMPORARY_FORCED = new MapWithExpiration<>(); + private static Ambassador instance; public static Ambassador getInstance() { return instance; @@ -61,12 +67,36 @@ public class Ambassador { } @Subscribe(order = PostOrder.LAST) - public void onProxyInitialization(ProxyInitializeEvent event) throws ReflectiveOperationException { + public void onProxyInitialization(ProxyInitializeEvent event) { initMetrics(); - server.getEventManager().register(this, new VelocityEventHandler(this)); + try { + Files.createDirectories(dataDirectory); - inject(); + Path configPath = dataDirectory.resolve("Ambassador.toml"); + config = AmbassadorConfig.read(configPath); + config.validate(); + + inject(); + + server.getEventManager().register(this, new VelocityEventHandler(this)); + } catch (Exception e) { + logger.error(e.toString()); + } + } + + @Subscribe + public void onProxyReload(ProxyReloadEvent event) { + try { + Path configPath = dataDirectory.resolve("Ambassador.toml"); + final AmbassadorConfig newconfig = AmbassadorConfig.read(configPath); + newconfig.validate(); + + config = newconfig; + } catch (Exception e) { + logger.error(e.toString()); + logger.warn("Reload unsuccessful, old config will be used."); + } } private void inject() throws ReflectiveOperationException { @@ -97,7 +127,35 @@ public class Ambassador { } + public static MapWithExpiration getTemporaryForced() { + return TEMPORARY_FORCED; + } + private void initMetrics() { Metrics metrics = metricsFactory.make(this, 15655); } + + public static class MapWithExpiration { + + private final Map> expirationMap = new HashMap<>(); + + + public V remove(K key) { + ExpiringValue expiringValue = expirationMap.remove(key); + if (expiringValue != null && expiringValue.value > System.currentTimeMillis()) { + return expiringValue.key; + } else { + return null; + } + } + + public void put(K key, V value, int expirationTime, TimeUnit unit) { + expirationMap.values().removeIf((v) -> v.value <= System.currentTimeMillis()); + expirationMap.put(key, new ExpiringValue<>(value,System.currentTimeMillis() + unit.toMillis(expirationTime))); + } + + private record ExpiringValue(K key, V value) { + } + + } } diff --git a/src/main/java/org/adde0109/ambassador/AmbassadorConfig.java b/src/main/java/org/adde0109/ambassador/AmbassadorConfig.java new file mode 100644 index 0000000..819aab7 --- /dev/null +++ b/src/main/java/org/adde0109/ambassador/AmbassadorConfig.java @@ -0,0 +1,67 @@ +package org.adde0109.ambassador; + +import com.electronwill.nightconfig.core.conversion.InvalidValueException; +import com.electronwill.nightconfig.core.file.CommentedFileConfig; +import com.google.gson.annotations.Expose; + +import java.net.URL; +import java.nio.file.Path; + +public class AmbassadorConfig { + + @Expose + private int resetTimeout = 1000; + + @Expose + private String disconnectResetMessage = "Please reconnect"; + + @Expose + private int serverSwitchCancellationTime = 120; + + private AmbassadorConfig(int resetTimeout, String kickResetMessage, int serverSwitchCancellationTime) { + this.resetTimeout = resetTimeout; + this.disconnectResetMessage = kickResetMessage; + this.serverSwitchCancellationTime = serverSwitchCancellationTime; + }; + + public void validate() { + final int connectionTimeout = Ambassador.getInstance().server.getConfiguration().getConnectTimeout(); + if (resetTimeout >= connectionTimeout) { + throw new InvalidValueException("'reset-timeout' can't be larger than nor equal to 'connection-timeout': reset-timeout=" + resetTimeout + " connection-timeout=" + connectionTimeout); + } + } + + public static AmbassadorConfig read(Path path) { + URL defaultConfigLocation = AmbassadorConfig.class.getClassLoader() + .getResource("default-ambassador.toml"); + if (defaultConfigLocation == null) { + throw new RuntimeException("Default configuration file does not exist."); + } + + CommentedFileConfig config = CommentedFileConfig.builder(path) + .defaultData(defaultConfigLocation) + .autosave() + .preserveInsertionOrder() + .sync() + .build(); + config.load(); + + int resetTimeout = config.getIntOrElse("reset-timeout", 3000); + String kickResetMessage = config.getOrElse("disconnect-reset-message", "Please reconnect"); + int serverSwitchCancellationTime = config.getIntOrElse("server-switch-cancellation-time", 120000); + + return new AmbassadorConfig(resetTimeout, kickResetMessage, serverSwitchCancellationTime); + } + + public int getResetTimeout() { + return resetTimeout; + } + + public String getDisconnectResetMessage() { + return disconnectResetMessage; + } + + public int getServerSwitchCancellationTime() { + return serverSwitchCancellationTime; + } +} diff --git a/src/main/java/org/adde0109/ambassador/forge/FML2CRPMClientConnectionPhase.java b/src/main/java/org/adde0109/ambassador/forge/FML2CRPMClientConnectionPhase.java index c1a1ddf..6ff7cfd 100644 --- a/src/main/java/org/adde0109/ambassador/forge/FML2CRPMClientConnectionPhase.java +++ b/src/main/java/org/adde0109/ambassador/forge/FML2CRPMClientConnectionPhase.java @@ -7,7 +7,6 @@ import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.config.PlayerInfoForwarding; import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.connection.MinecraftConnection; -import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.network.Connections; import com.velocitypowered.proxy.protocol.StateRegistry; @@ -51,7 +50,7 @@ public class FML2CRPMClientConnectionPhase extends VelocityForgeClientConnection ScheduledFuture scheduledFuture = connection.eventLoop().schedule(()-> { connection.getChannel().pipeline().remove(ForgeConstants.RESET_LISTENER); future.complete(false); - },5, TimeUnit.SECONDS); + }, Ambassador.getInstance().config.getResetTimeout(), TimeUnit.MILLISECONDS); connection.getChannel().pipeline().addBefore(Connections.MINECRAFT_DECODER, ForgeConstants.RESET_LISTENER,new FML2CRPMResetCompleteDecoder()); getPayloadManager().listenFor(98).thenAccept(ignore -> { if (scheduledFuture.cancel(false)) { diff --git a/src/main/java/org/adde0109/ambassador/forge/FML2ClientConnectionPhase.java b/src/main/java/org/adde0109/ambassador/forge/FML2ClientConnectionPhase.java index ae297a7..eaea17b 100644 --- a/src/main/java/org/adde0109/ambassador/forge/FML2ClientConnectionPhase.java +++ b/src/main/java/org/adde0109/ambassador/forge/FML2ClientConnectionPhase.java @@ -9,10 +9,9 @@ import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; import io.netty.buffer.ByteBuf; -import io.netty.util.ReferenceCountUtil; import net.kyori.adventure.text.Component; +import org.adde0109.ambassador.Ambassador; import org.adde0109.ambassador.velocity.VelocityForgeClientConnectionPhase; -import org.apache.commons.collections4.map.PassiveExpiringMap; import java.lang.reflect.Method; import java.util.Arrays; @@ -21,10 +20,6 @@ import java.util.concurrent.TimeUnit; public class FML2ClientConnectionPhase extends VelocityForgeClientConnectionPhase { - private static String OUTBOUND_CATCHER_NAME = "ambassador-catcher"; - - private static final PassiveExpiringMap TEMPORARY_FORCED = new PassiveExpiringMap<>(120, TimeUnit.SECONDS); - private Throwable throwable; private RegisteredServer triedServer; private Continuation continuation; @@ -56,7 +51,7 @@ public class FML2ClientConnectionPhase extends VelocityForgeClientConnectionPhas this.continuation = continuation; final MinecraftConnection connection = player.getConnection(); - forced = TEMPORARY_FORCED.remove(player.getUsername()); + forced = Ambassador.getTemporaryForced().remove(player.getUsername()); if (forced != null) { player.createConnectionRequest(forced).fireAndForget(); } else { @@ -75,8 +70,8 @@ public class FML2ClientConnectionPhase extends VelocityForgeClientConnectionPhas CompletableFuture future = newPhase.reset(server,player); future.thenAccept(success -> { if (!success) { - TEMPORARY_FORCED.put(player.getUsername(),server); - player.disconnect(Component.text("Please reconnect")); + Ambassador.getTemporaryForced().put(player.getUsername(),server, Ambassador.getInstance().config.getServerSwitchCancellationTime(), TimeUnit.SECONDS); + player.disconnect(Component.text(Ambassador.getInstance().config.getDisconnectResetMessage())); } }); return future; diff --git a/src/main/java/org/adde0109/ambassador/forge/ForgeFMLConnectionType.java b/src/main/java/org/adde0109/ambassador/forge/ForgeFMLConnectionType.java index 8d6ffe1..ccf6d7d 100644 --- a/src/main/java/org/adde0109/ambassador/forge/ForgeFMLConnectionType.java +++ b/src/main/java/org/adde0109/ambassador/forge/ForgeFMLConnectionType.java @@ -29,6 +29,7 @@ public class ForgeFMLConnectionType implements ConnectionType { @Override public GameProfile addGameProfileTokensIfRequired(GameProfile original, PlayerInfoForwarding forwardingType) { + //This is meant for Arc light to parse if (forwardingType == PlayerInfoForwarding.LEGACY) { return original.addProperties(Collections.singleton(new GameProfile.Property("extraData", "\1FML" + netVersion + "\1", ""))); } else { diff --git a/src/main/resources/default-ambassador.toml b/src/main/resources/default-ambassador.toml new file mode 100644 index 0000000..d5b2f43 --- /dev/null +++ b/src/main/resources/default-ambassador.toml @@ -0,0 +1,9 @@ +# Do not change this +config-version = "1.0" + +# How long to wait for the client to reset before disconnecting (In milliseconds) +reset-timeout = 1000 +# Message displayed to the player when disconnected from proxy during server switch. +disconnect-reset-message = "Please reconnect" +# How long the player has to reconnect before canceling the server switch. (In seconds) +server-switch-cancellation-time = 120