Compare commits
5 Commits
v1.5.2-bet
...
non-api
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbf12f90e7 | ||
|
|
b2a9eeffa1 | ||
|
|
eaa974593c | ||
|
|
0fcf6af72f | ||
|
|
c7c2e1cd33 |
|
|
@ -3,7 +3,8 @@
|
||||||
This is a Velocity plugin that makes it possible to host a modern Forge server behind a Velocity proxy!
|
This is a Velocity plugin that makes it possible to host a modern Forge server behind a Velocity proxy!
|
||||||
|
|
||||||
Unlike other solutions, this plugin does not require any special modifications to the backend server nor the client. (The player doesn't need to do anything)
|
Unlike other solutions, this plugin does not require any special modifications to the backend server nor the client. (The player doesn't need to do anything)
|
||||||
|
## Only for 1.13-1.20.1
|
||||||
|
Velocity has now added built-in support for newer mc versions and therefore don't need Ambassador. You might still need PCF mod on the server-side, for more info please visit: https://github.com/adde0109/Proxy-Compatible-Forge
|
||||||
## How to get started:
|
## How to get started:
|
||||||
1. Download and install this plugin to your proxy.
|
1. Download and install this plugin to your proxy.
|
||||||
2. After starting the server, configure the plugin it to your liking using the config file found in the folder "Ambassador".
|
2. After starting the server, configure the plugin it to your liking using the config file found in the folder "Ambassador".
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ plugins {
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "org.adde0109"
|
group = "org.adde0109"
|
||||||
version = "1.5.2-beta"
|
version = "1.5.3-beta"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import java.util.Map;
|
||||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||||
|
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||||
import com.velocitypowered.proxy.network.ConnectionManager;
|
import com.velocitypowered.proxy.network.ConnectionManager;
|
||||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||||
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
|
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
|
||||||
|
|
@ -28,6 +29,8 @@ import com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentPropertySeria
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
import io.netty.channel.ChannelInitializer;
|
import io.netty.channel.ChannelInitializer;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
import org.adde0109.ambassador.velocity.VelocityBackendChannelInitializer;
|
import org.adde0109.ambassador.velocity.VelocityBackendChannelInitializer;
|
||||||
import org.adde0109.ambassador.velocity.VelocityServerChannelInitializer;
|
import org.adde0109.ambassador.velocity.VelocityServerChannelInitializer;
|
||||||
import org.adde0109.ambassador.velocity.VelocityEventHandler;
|
import org.adde0109.ambassador.velocity.VelocityEventHandler;
|
||||||
|
|
@ -43,7 +46,7 @@ import java.util.concurrent.TimeUnit;
|
||||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19;
|
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19;
|
||||||
import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.mapSet;
|
import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.mapSet;
|
||||||
|
|
||||||
@Plugin(id = "ambassador", name = "Ambassador", version = "1.5.2-beta", authors = {"adde0109"})
|
@Plugin(id = "ambassador", name = "Ambassador", version = "1.5.3-beta", authors = {"adde0109"})
|
||||||
public class Ambassador {
|
public class Ambassador {
|
||||||
|
|
||||||
//Don't forget to update checkCompatibleVersion() when changing this value
|
//Don't forget to update checkCompatibleVersion() when changing this value
|
||||||
|
|
@ -153,6 +156,16 @@ public class Ambassador {
|
||||||
return TEMPORARY_FORCED;
|
return TEMPORARY_FORCED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void reconnectSwitchPlayer(ConnectedPlayer player) {
|
||||||
|
TEMPORARY_FORCED.put(player.getUsername(), player.getConnectionInFlight().getServer(),
|
||||||
|
config.getServerSwitchCancellationTime(), TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
MiniMessage mm = MiniMessage.miniMessage();
|
||||||
|
Component parsed = mm.deserialize(config.getKickReconnectMessageString());
|
||||||
|
|
||||||
|
player.disconnect(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
private void initMetrics() {
|
private void initMetrics() {
|
||||||
Metrics metrics = metricsFactory.make(this, 15655);
|
Metrics metrics = metricsFactory.make(this, 15655);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,92 +14,119 @@ import java.nio.file.Path;
|
||||||
|
|
||||||
public class AmbassadorConfig {
|
public class AmbassadorConfig {
|
||||||
|
|
||||||
@Expose
|
@Expose
|
||||||
private int serverSwitchCancellationTime = 30;
|
private int serverSwitchCancellationTime = 30;
|
||||||
|
|
||||||
@Expose
|
@Expose
|
||||||
private boolean silenceWarnings = false;
|
private boolean silenceWarnings = false;
|
||||||
|
@Expose
|
||||||
|
private boolean bypassRegistryCheck = false;
|
||||||
|
@Expose
|
||||||
|
private boolean bypassModCheck = false;
|
||||||
|
|
||||||
@Expose
|
@Expose
|
||||||
private boolean bypassRegistryCheck = false;
|
private boolean debugMode = false;
|
||||||
@Expose
|
|
||||||
private boolean bypassModCheck = false;
|
|
||||||
|
|
||||||
@Expose
|
@Expose
|
||||||
private boolean debugMode = false;
|
private boolean enableKickReset = false;
|
||||||
|
|
||||||
private AmbassadorConfig(boolean silenceWarnings, boolean bypassRegistryCheck, boolean bypassModCheck, boolean debugMode) {
|
@Expose
|
||||||
this.silenceWarnings = silenceWarnings;
|
private String kickReconnectMessageString = "<red>Please reconnect.</red>";
|
||||||
this.bypassRegistryCheck = bypassRegistryCheck;
|
|
||||||
this.bypassModCheck = bypassModCheck;
|
|
||||||
this.debugMode = debugMode;
|
|
||||||
};
|
|
||||||
|
|
||||||
public static AmbassadorConfig read(Path path) throws IOException {
|
private AmbassadorConfig(boolean silenceWarnings, boolean bypassRegistryCheck, boolean bypassModCheck,
|
||||||
URL defaultConfigLocation = AmbassadorConfig.class.getClassLoader()
|
boolean debugMode, boolean enableKickReset, String kickReconnectMessageString) {
|
||||||
.getResource("default-ambassador.toml");
|
this.silenceWarnings = silenceWarnings;
|
||||||
if (defaultConfigLocation == null) {
|
this.bypassRegistryCheck = bypassRegistryCheck;
|
||||||
throw new RuntimeException("Default configuration file does not exist.");
|
this.bypassModCheck = bypassModCheck;
|
||||||
|
this.debugMode = debugMode;
|
||||||
|
this.enableKickReset = enableKickReset;
|
||||||
|
this.kickReconnectMessageString = kickReconnectMessageString;
|
||||||
}
|
}
|
||||||
|
|
||||||
CommentedFileConfig config = CommentedFileConfig.builder(path)
|
public static AmbassadorConfig read(Path path) throws IOException {
|
||||||
.defaultData(defaultConfigLocation)
|
URL defaultConfigLocation = AmbassadorConfig.class.getClassLoader()
|
||||||
.autosave()
|
.getResource("default-ambassador.toml");
|
||||||
.preserveInsertionOrder()
|
if (defaultConfigLocation == null) {
|
||||||
.sync()
|
throw new RuntimeException("Default configuration file does not exist.");
|
||||||
.build();
|
}
|
||||||
config.load();
|
|
||||||
|
|
||||||
double configVersion;
|
CommentedFileConfig config = CommentedFileConfig.builder(path)
|
||||||
try {
|
.defaultData(defaultConfigLocation)
|
||||||
configVersion = Double.parseDouble(config.getOrElse("config-version", "1.0"));
|
.autosave()
|
||||||
} catch (NumberFormatException e) {
|
.preserveInsertionOrder()
|
||||||
configVersion = 1.0;
|
.sync()
|
||||||
|
.build();
|
||||||
|
config.load();
|
||||||
|
|
||||||
|
double configVersion;
|
||||||
|
try {
|
||||||
|
configVersion = Double.parseDouble(config.getOrElse("config-version", "1.0"));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
configVersion = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean silenceWarnings = config.getOrElse("silence-warnings", false);
|
||||||
|
|
||||||
|
int serverSwitchCancellationTime = config.getOrElse("serverRedirectTimeout", 30);
|
||||||
|
|
||||||
|
boolean bypassRegistryCheck = config.getOrElse("bypass-registry-checks", false);
|
||||||
|
|
||||||
|
boolean bypassModCheck = config.getOrElse("bypass-mod-checks", false);
|
||||||
|
|
||||||
|
boolean debugMode = config.getOrElse("debug-mode", false);
|
||||||
|
|
||||||
|
String kickReconnectMessageString = config.getOrElse("disconnect-reset-message",
|
||||||
|
config.getOrElse("reconnect-message", "<red>Please reconnect.</red>"));
|
||||||
|
|
||||||
|
//Upgrade config
|
||||||
|
if (configVersion <= 2.0) {
|
||||||
|
Files.delete(path);
|
||||||
|
config = CommentedFileConfig.builder(path)
|
||||||
|
.defaultData(defaultConfigLocation)
|
||||||
|
.autosave()
|
||||||
|
.preserveInsertionOrder()
|
||||||
|
.sync()
|
||||||
|
.build();
|
||||||
|
config.load();
|
||||||
|
config.set("silence-warnings", silenceWarnings);
|
||||||
|
config.set("serverRedirectTimeout", serverSwitchCancellationTime);
|
||||||
|
config.set("bypass-registry-checks", bypassRegistryCheck);
|
||||||
|
config.set("bypass-mod-checks", bypassModCheck);
|
||||||
|
config.set("debug-mode", debugMode);
|
||||||
|
config.set("reconnect-message", kickReconnectMessageString);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean enableKickReset = config.getOrElse("enable-kick-reset", false);
|
||||||
|
|
||||||
|
return new AmbassadorConfig(silenceWarnings, bypassRegistryCheck, bypassModCheck,
|
||||||
|
debugMode, enableKickReset, kickReconnectMessageString);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean silenceWarnings = config.getOrElse("silence-warnings", false);
|
public int getServerSwitchCancellationTime() {
|
||||||
|
return serverSwitchCancellationTime;
|
||||||
//Upgrade config
|
|
||||||
if (configVersion <= 1.2) {
|
|
||||||
Files.delete(path);
|
|
||||||
config = CommentedFileConfig.builder(path)
|
|
||||||
.defaultData(defaultConfigLocation)
|
|
||||||
.autosave()
|
|
||||||
.preserveInsertionOrder()
|
|
||||||
.sync()
|
|
||||||
.build();
|
|
||||||
config.load();
|
|
||||||
config.set("silence-warnings", silenceWarnings);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int serverSwitchCancellationTime = config.getOrElse("serverRedirectTimeout", 30);
|
public boolean isSilenceWarnings() {
|
||||||
|
return silenceWarnings;
|
||||||
|
}
|
||||||
|
|
||||||
boolean bypassRegistryCheck = config.getOrElse("bypass-registry-checks", false);
|
public boolean isBypassRegistryCheck() {
|
||||||
|
return bypassRegistryCheck;
|
||||||
|
}
|
||||||
|
|
||||||
boolean bypassModCheck = config.getOrElse("bypass-mod-checks", false);
|
public boolean isBypassModCheck() {
|
||||||
|
return bypassModCheck;
|
||||||
|
}
|
||||||
|
|
||||||
boolean debugMode = config.getOrElse("debug-mode", false);
|
public boolean isDebugMode() {
|
||||||
|
return debugMode;
|
||||||
|
}
|
||||||
|
|
||||||
return new AmbassadorConfig(bypassRegistryCheck, bypassModCheck, silenceWarnings, debugMode);
|
public boolean isEnableKickReset() {
|
||||||
}
|
return enableKickReset;
|
||||||
|
}
|
||||||
|
|
||||||
public int getServerSwitchCancellationTime() {
|
public String getKickReconnectMessageString() {
|
||||||
return serverSwitchCancellationTime;
|
return kickReconnectMessageString;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSilenceWarnings() {
|
|
||||||
return silenceWarnings;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isBypassRegistryCheck() {
|
|
||||||
return bypassRegistryCheck;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isBypassModCheck() {
|
|
||||||
return bypassModCheck;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDebugMode() {
|
|
||||||
return debugMode;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,10 @@ public enum VelocityForgeBackendConnectionPhase implements BackendConnectionPhas
|
||||||
VelocityForgeClientConnectionPhase clientPhase = (VelocityForgeClientConnectionPhase) player.getPhase();
|
VelocityForgeClientConnectionPhase clientPhase = (VelocityForgeClientConnectionPhase) player.getPhase();
|
||||||
|
|
||||||
|
|
||||||
|
if (!player.isActive()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!clientPhase.consideredComplete()) {
|
if (!clientPhase.consideredComplete()) {
|
||||||
//Initial Forge
|
//Initial Forge
|
||||||
if (message instanceof ModListPacket modListPacket) {
|
if (message instanceof ModListPacket modListPacket) {
|
||||||
|
|
@ -92,10 +96,16 @@ public enum VelocityForgeBackendConnectionPhase implements BackendConnectionPhas
|
||||||
|
|
||||||
if (clientPhase.forgeHandshake.getModListReplyPacket() == null) {
|
if (clientPhase.forgeHandshake.getModListReplyPacket() == null) {
|
||||||
//We have nothing to respond with during this handshake. Unable to proceed.
|
//We have nothing to respond with during this handshake. Unable to proceed.
|
||||||
Ambassador.getInstance().logger.error("Unable for {} to switch servers. " +
|
if (Ambassador.getInstance().config.isEnableKickReset()) {
|
||||||
"Vanilla({}) -> Forge({}) switch without reset is not yet supported!", player.getGameProfile().getName(),
|
//Kick-reset
|
||||||
player.getConnectedServer().getServerInfo().getName(), server.getServerInfo().getName());
|
Ambassador.getInstance().reconnectSwitchPlayer(player);
|
||||||
server.disconnect();
|
} else {
|
||||||
|
Ambassador.getInstance().logger.error("Unable for {} to switch servers. Vanilla({}) -> Forge({}) switch " +
|
||||||
|
"without client side mod or kick-reset enabled is not yet supported!",
|
||||||
|
player.getGameProfile().getName(), player.getConnectedServer().getServerInfo().getName(),
|
||||||
|
server.getServerInfo().getName());
|
||||||
|
server.disconnect();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -124,6 +134,9 @@ public enum VelocityForgeBackendConnectionPhase implements BackendConnectionPhas
|
||||||
if (Ambassador.getInstance().config.isBypassRegistryCheck() ||
|
if (Ambassador.getInstance().config.isBypassRegistryCheck() ||
|
||||||
clientPhase.forgeHandshake.isCompatible(handshake)) {
|
clientPhase.forgeHandshake.isCompatible(handshake)) {
|
||||||
server.ensureConnected().write(clientPhase.forgeHandshake.getModListReplyPacket());
|
server.ensureConnected().write(clientPhase.forgeHandshake.getModListReplyPacket());
|
||||||
|
} else if (Ambassador.getInstance().config.isEnableKickReset()) {
|
||||||
|
//Kick-reset
|
||||||
|
Ambassador.getInstance().reconnectSwitchPlayer(player);
|
||||||
} else {
|
} else {
|
||||||
Ambassador.getInstance().logger.error("Unable to switch due to the registries of " +
|
Ambassador.getInstance().logger.error("Unable to switch due to the registries of " +
|
||||||
server.getServer().getServerInfo().getName() + " being different from the registries of " +
|
server.getServer().getServerInfo().getName() + " being different from the registries of " +
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import org.adde0109.ambassador.velocity.client.OutboundSuccessHolder;
|
||||||
import org.adde0109.ambassador.velocity.client.ClientPacketQueue;
|
import org.adde0109.ambassador.velocity.client.ClientPacketQueue;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase {
|
public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase {
|
||||||
|
|
||||||
|
|
@ -255,6 +256,8 @@ public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase
|
||||||
buf.writeBytes((player.getVirtualHost().get().getHostName() + ":"
|
buf.writeBytes((player.getVirtualHost().get().getHostName() + ":"
|
||||||
+ player.getVirtualHost().get().getPort()).getBytes(StandardCharsets.UTF_8));
|
+ player.getVirtualHost().get().getPort()).getBytes(StandardCharsets.UTF_8));
|
||||||
player.getConnection().write(new PluginMessagePacket("srvredirect:red", buf));
|
player.getConnection().write(new PluginMessagePacket("srvredirect:red", buf));
|
||||||
|
|
||||||
|
Ambassador.getInstance().reconnectSwitchPlayer(player);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# Do not change this
|
# Do not change this
|
||||||
config-version = "2.0"
|
config-version = "2.1"
|
||||||
|
|
||||||
# How much time the player has to reconnect before canceling the server switch. (In seconds)
|
# How much time the player has to reconnect before canceling the server switch. (In seconds)
|
||||||
# Only for players with ServerRedirect mod installed. Set to -1 to disable ServerRedirect mod support.
|
# Only for players with ServerRedirect mod installed. Set to -1 to disable ServerRedirect mod support.
|
||||||
|
|
@ -17,5 +17,13 @@ bypass-registry-checks = false
|
||||||
# Warning: This is a safety measure for when bypassRegistryCheck is true. Setting this to also true can cause crashes.
|
# Warning: This is a safety measure for when bypassRegistryCheck is true. Setting this to also true can cause crashes.
|
||||||
bypass-mod-checks = false
|
bypass-mod-checks = false
|
||||||
|
|
||||||
#Only for debug/troubleshooting
|
# Only for debug/troubleshooting
|
||||||
debug-mode = false
|
debug-mode = false
|
||||||
|
|
||||||
|
# Make the player reconnect when server switching as a last resort if
|
||||||
|
# the player can't reset or switch servers without resetting.
|
||||||
|
enable-kick-reset = false
|
||||||
|
|
||||||
|
# Message to display when player gets kicked due to server switching according to enable-kick-reset.
|
||||||
|
# This input is deserialzied as MiniMessage and support formating according to the MiniMessage format.
|
||||||
|
reconnect-message = "<red>Please reconnect.</red>"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user