diff --git a/ansible/files/proxy-config/plugins/zorweth/config.yml b/ansible/files/proxy-config/plugins/zorweth/config.yml index e46bb279e..3ffa3a10c 100644 --- a/ansible/files/proxy-config/plugins/zorweth/config.yml +++ b/ansible/files/proxy-config/plugins/zorweth/config.yml @@ -10,5 +10,8 @@ database: idle_timeout: 600000 max_lifetime: 7200000 -holding-server: "" +holding-server: "pvp" failure-message: "Unable to verify your rocket transfer. Please reconnect and try again." +main-server: main +zorweth-server: zorweth +default-server: main diff --git a/plugins/civproxy-velocity/build.gradle.kts b/plugins/civproxy-velocity/build.gradle.kts index d31995248..479876383 100644 --- a/plugins/civproxy-velocity/build.gradle.kts +++ b/plugins/civproxy-velocity/build.gradle.kts @@ -11,4 +11,5 @@ dependencies { compileOnly(libs.luckperms.api) annotationProcessor(libs.velocity.api) api(project(":libraries:name-api")) + compileOnly(project(":plugins:zorweth-velocity")) } diff --git a/plugins/civproxy-velocity/src/main/java/net/civmc/civproxy/CivProxyPlugin.java b/plugins/civproxy-velocity/src/main/java/net/civmc/civproxy/CivProxyPlugin.java index 197ce4df8..62c277cf8 100644 --- a/plugins/civproxy-velocity/src/main/java/net/civmc/civproxy/CivProxyPlugin.java +++ b/plugins/civproxy-velocity/src/main/java/net/civmc/civproxy/CivProxyPlugin.java @@ -5,9 +5,11 @@ import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; import com.velocitypowered.api.plugin.Dependency; import com.velocitypowered.api.plugin.Plugin; +import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.plugin.annotation.DataDirectory; import com.velocitypowered.api.proxy.ProxyServer; import com.zaxxer.hikari.HikariConfig; +import java.util.Optional; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; @@ -15,11 +17,12 @@ import java.nio.file.Path; import javax.sql.DataSource; import com.zaxxer.hikari.HikariDataSource; import net.civmc.civproxy.renamer.PlayerRenamer; +import net.civmc.zorweth.velocity.ZorwethVelocityPlugin; import org.slf4j.Logger; import org.spongepowered.configurate.CommentedConfigurationNode; import org.spongepowered.configurate.yaml.YamlConfigurationLoader; -@Plugin(id = "civproxy", name = "CivProxy", version = "1.0.0", authors = {"Okx"}, dependencies = {@Dependency(id = "ajqueue"), @Dependency(id = "luckperms")}) +@Plugin(id = "civproxy", name = "CivProxy", version = "1.0.0", authors = {"Okx"}, dependencies = {@Dependency(id = "ajqueue"), @Dependency(id = "luckperms"), @Dependency(id = "zorweth")}) public class CivProxyPlugin { private final ProxyServer server; @@ -46,7 +49,14 @@ public class CivProxyPlugin { new PlayerCount(this, server).start(); new PlayerRenamer(this, server, source).start(); if (server.getPluginManager().isLoaded("ajqueue")) { - new QueueListener(this, server).start(); + final Optional zorweth = server.getPluginManager().getPlugin("zorweth") + .flatMap(PluginContainer::getInstance) + .map(ZorwethVelocityPlugin.class::cast); + if (zorweth.isPresent()) { + new QueueListener(this, server, zorweth.get()).start(); + } else { + this.logger.error("Zorweth is required for queue routing, but its plugin instance was not available"); + } } } diff --git a/plugins/civproxy-velocity/src/main/java/net/civmc/civproxy/QueueListener.java b/plugins/civproxy-velocity/src/main/java/net/civmc/civproxy/QueueListener.java index 36b38ae0f..fc04efdd5 100644 --- a/plugins/civproxy-velocity/src/main/java/net/civmc/civproxy/QueueListener.java +++ b/plugins/civproxy-velocity/src/main/java/net/civmc/civproxy/QueueListener.java @@ -9,17 +9,22 @@ import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.server.RegisteredServer; +import java.sql.SQLException; import java.time.Instant; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; +import net.civmc.zorweth.velocity.ZorwethVelocityPlugin; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import net.luckperms.api.LuckPermsProvider; import net.luckperms.api.model.data.TemporaryNodeMergeStrategy; import net.luckperms.api.model.user.UserManager; import net.luckperms.api.node.types.PermissionNode; import us.ajg0702.queue.api.AjQueueAPI; import us.ajg0702.queue.api.QueueManager; +import us.ajg0702.queue.api.events.PreConnectEvent; +import us.ajg0702.queue.api.events.PreQueueEvent; import us.ajg0702.queue.api.players.AdaptedPlayer; public class QueueListener { @@ -29,10 +34,12 @@ public class QueueListener { private final CivProxyPlugin plugin; private final ProxyServer server; + private final ZorwethVelocityPlugin zorweth; - public QueueListener(CivProxyPlugin plugin, ProxyServer server) { + public QueueListener(CivProxyPlugin plugin, ProxyServer server, ZorwethVelocityPlugin zorweth) { this.plugin = plugin; this.server = server; + this.zorweth = zorweth; } record QueueRecord(Instant instant, String server) { @@ -45,6 +52,8 @@ public class QueueListener { public void start() { server.getEventManager().register(plugin, this); + AjQueueAPI.getInstance().listen(PreQueueEvent.class, this::onPreQueue); + AjQueueAPI.getInstance().listen(PreConnectEvent.class, this::onPreQueueConnect); } @Subscribe @@ -93,22 +102,62 @@ public class QueueListener { if (event.getPreviousServer() != null) { return; } - RegisteredServer mainServer = event.getOriginalServer(); - String name = mainServer.getServerInfo().getName(); + RegisteredServer requestedServer = event.getOriginalServer(); + String name = requestedServer.getServerInfo().getName(); if (name.equals("pvp")) { return; } - if (mainServer.getPlayersConnected().size() >= 145 && !event.getPlayer().hasPermission("joinbypass.use")) { -// RegisteredServer mini = server.getServer("mini").orElse(null); -// if (mini != null && mini.getPlayersConnected().size() < 110) { -// event.setResult(ServerPreConnectEvent.ServerResult.allowed(server.getServer("mini").get())); -// } else { - event.setResult(ServerPreConnectEvent.ServerResult.allowed(server.getServer("pvp").get())); -// } - - players.put(event.getPlayer(), new QueueRecord(Instant.now(), name)); + final RegisteredServer queueTarget; + if (this.zorweth.isProtectedServer(name) && !this.zorweth.canBypass(event.getPlayer())) { + final String expectedServer = getExpectedServer(event.getPlayer()); + if (expectedServer == null) { + event.getPlayer().disconnect(Component.text("Unable to verify your rocket route. Please reconnect and try again.", NamedTextColor.RED)); + return; + } + queueTarget = this.server.getServer(expectedServer).orElse(null); + if (queueTarget == null) { + this.plugin.getLogger().error("Unable to find expected rocket route server {}", expectedServer); + event.getPlayer().disconnect(Component.text("Unable to verify your rocket route. Please reconnect and try again.", NamedTextColor.RED)); + return; + } + } else { + queueTarget = requestedServer; } + + if (queueTarget.getPlayersConnected().size() >= 145 && !event.getPlayer().hasPermission("joinbypass.use")) { + event.setResult(ServerPreConnectEvent.ServerResult.allowed(server.getServer("pvp").get())); + + players.put(event.getPlayer(), new QueueRecord(Instant.now(), queueTarget.getServerInfo().getName())); + } else if (queueTarget != requestedServer) { + event.setResult(ServerPreConnectEvent.ServerResult.allowed(queueTarget)); + } + } + + private void onPreQueue(final PreQueueEvent event) { + if (!this.zorweth.isProtectedServer(event.getTarget().getName()) || event.getPlayer().hasPermission("zorweth.admin")) { + return; + } + final String expectedServer = getExpectedServer(event.getPlayer()); + if (expectedServer != null && event.getTarget().getName().equals(expectedServer)) { + return; + } + event.setCancelled(true); + event.getPlayer().sendMessage(Component.text("You cannot queue for that server unless your rocket route allows it.", NamedTextColor.RED)); + } + + private void onPreQueueConnect(final PreConnectEvent event) { + final String targetName = event.getTargetServer().getName(); + if (!this.zorweth.isProtectedServer(targetName) || event.getPlayer().getPlayer().hasPermission("zorweth.admin")) { + return; + } + final String expectedServer = getExpectedServer(event.getPlayer().getPlayer()); + if (targetName.equals(expectedServer)) { + return; + } + event.setCancelled(true); + event.getPlayer().getPlayer().sendMessage(Component.text("Your rocket route changed. Please queue for " + + (expectedServer == null ? "the correct server" : expectedServer) + ".", NamedTextColor.RED)); } @Subscribe @@ -180,4 +229,22 @@ public class QueueListener { userManager.saveUser(user); }); } + + private String getExpectedServer(final Player player) { + try { + return this.zorweth.getExpectedServer(player.getUniqueId()); + } catch (final SQLException exception) { + this.plugin.getLogger().error("Failed to look up rocket route for " + player.getUniqueId(), exception); + return null; + } + } + + private String getExpectedServer(final AdaptedPlayer player) { + try { + return this.zorweth.getExpectedServer(player.getUniqueId()); + } catch (final SQLException exception) { + this.plugin.getLogger().error("Failed to look up rocket route for " + player.getUniqueId(), exception); + return null; + } + } } diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/space/BuildRocketRecipe.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/space/BuildRocketRecipe.java index ea7dd66d7..1d51aa4ba 100644 --- a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/space/BuildRocketRecipe.java +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/space/BuildRocketRecipe.java @@ -168,6 +168,7 @@ public class BuildRocketRecipe extends InputRecipe { if (!FactoryMod.getInstance().getManager().isCitadelEnabled()) { return; } + // TODO destroy this and carry it over to the other server when flight is launched final Reinforcement furnaceReinforcement = ReinforcementLogic.getReinforcementAt(fccf.getFurnace().getLocation()); if (furnaceReinforcement == null) { return; diff --git a/plugins/zorweth-paper/src/main/java/net/civmc/zorweth/DestinationTransferListener.java b/plugins/zorweth-paper/src/main/java/net/civmc/zorweth/DestinationTransferListener.java index 74959557c..5a706d351 100644 --- a/plugins/zorweth-paper/src/main/java/net/civmc/zorweth/DestinationTransferListener.java +++ b/plugins/zorweth-paper/src/main/java/net/civmc/zorweth/DestinationTransferListener.java @@ -208,6 +208,7 @@ public final class DestinationTransferListener implements Listener { }); } + // TODO can't land in city/vault bastions private RocketBlockPosition findRocketPosition(final DestinationRocketTransfer transfer) { int rx = transfer.requestedX() - FlightComputer.RELATIVE_POSITION.getX(); int rz = transfer.requestedZ() - FlightComputer.RELATIVE_POSITION.getZ(); diff --git a/plugins/zorweth-paper/src/main/java/net/civmc/zorweth/database/RocketTransferDao.java b/plugins/zorweth-paper/src/main/java/net/civmc/zorweth/database/RocketTransferDao.java index 1c8bd1561..a65bd3f26 100644 --- a/plugins/zorweth-paper/src/main/java/net/civmc/zorweth/database/RocketTransferDao.java +++ b/plugins/zorweth-paper/src/main/java/net/civmc/zorweth/database/RocketTransferDao.java @@ -36,6 +36,7 @@ public final class RocketTransferDao { insertTransfer(connection, manifest); insertPassengers(connection, passengers); insertChests(connection, chests); + upsertRoutes(connection, manifest, passengers); connection.commit(); } catch (final SQLException exception) { connection.rollback(); @@ -267,6 +268,27 @@ public final class RocketTransferDao { } } + private void upsertRoutes(final Connection connection, final RocketManifest manifest, + final Iterable passengers) throws SQLException { + try (PreparedStatement statement = connection.prepareStatement(""" + INSERT INTO rocket_player_routes (player_uuid, expected_server, source, transfer_id) + VALUES (?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + expected_server = VALUES(expected_server), + source = VALUES(source), + transfer_id = VALUES(transfer_id) + """)) { + for (final RocketPassengerTransfer passenger : passengers) { + statement.setString(1, passenger.playerUuid().toString()); + statement.setString(2, manifest.destinationServer()); + statement.setString(3, "ROCKET"); + statement.setString(4, manifest.transferId().toString()); + statement.addBatch(); + } + statement.executeBatch(); + } + } + private void insertChests(final Connection connection, final Iterable chests) throws SQLException { try (PreparedStatement statement = connection.prepareStatement(""" diff --git a/plugins/zorweth-paper/src/main/java/net/civmc/zorweth/database/ZorwethDatabase.java b/plugins/zorweth-paper/src/main/java/net/civmc/zorweth/database/ZorwethDatabase.java index 23e232c3f..da0284a71 100644 --- a/plugins/zorweth-paper/src/main/java/net/civmc/zorweth/database/ZorwethDatabase.java +++ b/plugins/zorweth-paper/src/main/java/net/civmc/zorweth/database/ZorwethDatabase.java @@ -85,6 +85,37 @@ public final class ZorwethDatabase { ) """); + migrator.registerMigration("zorweth", 1, + """ + CREATE TABLE IF NOT EXISTS rocket_player_routes ( + player_uuid VARCHAR(36) NOT NULL, + expected_server VARCHAR(64) NOT NULL, + source VARCHAR(32) NOT NULL, + transfer_id VARCHAR(36), + updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), + PRIMARY KEY (player_uuid), + INDEX idx_rocket_player_routes_transfer (transfer_id) + ) + """, + """ + INSERT INTO rocket_player_routes (player_uuid, expected_server, source, transfer_id) + SELECT rtp.player_uuid, rt.destination_server, 'ROCKET', rt.transfer_id + FROM rocket_transfer_players rtp + JOIN rocket_transfers rt ON rt.transfer_id = rtp.transfer_id + JOIN ( + SELECT rtp_latest.player_uuid, MAX(rt_latest.created_at) AS created_at + FROM rocket_transfer_players rtp_latest + JOIN rocket_transfers rt_latest ON rt_latest.transfer_id = rtp_latest.transfer_id + WHERE rt_latest.state <> 'CANCELLED' + GROUP BY rtp_latest.player_uuid + ) latest ON latest.player_uuid = rtp.player_uuid AND latest.created_at = rt.created_at + WHERE rt.state <> 'CANCELLED' + ON DUPLICATE KEY UPDATE + expected_server = VALUES(expected_server), + source = VALUES(source), + transfer_id = VALUES(transfer_id) + """); + try (Connection connection = dataSource.getConnection()) { migrator.migrate(connection); } diff --git a/plugins/zorweth-velocity/src/main/java/net/civmc/zorweth/velocity/ZorwethVelocityPlugin.java b/plugins/zorweth-velocity/src/main/java/net/civmc/zorweth/velocity/ZorwethVelocityPlugin.java index 1b7c7f3fa..2223663ea 100644 --- a/plugins/zorweth-velocity/src/main/java/net/civmc/zorweth/velocity/ZorwethVelocityPlugin.java +++ b/plugins/zorweth-velocity/src/main/java/net/civmc/zorweth/velocity/ZorwethVelocityPlugin.java @@ -1,15 +1,18 @@ package net.civmc.zorweth.velocity; import com.google.inject.Inject; +import com.velocitypowered.api.command.CommandManager; +import com.velocitypowered.api.command.CommandMeta; +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.command.SimpleCommand; import com.velocitypowered.api.event.Subscribe; -import com.velocitypowered.api.event.player.ServerPostConnectEvent; import com.velocitypowered.api.event.player.ServerPreConnectEvent; import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; import com.velocitypowered.api.plugin.Plugin; import com.velocitypowered.api.plugin.annotation.DataDirectory; +import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; -import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.server.RegisteredServer; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; @@ -23,6 +26,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Optional; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.slf4j.Logger; @@ -32,10 +36,6 @@ import org.spongepowered.configurate.yaml.YamlConfigurationLoader; @Plugin(id = "zorweth", name = "Zorweth", version = "1.0.0", authors = {"Okx"}) public final class ZorwethVelocityPlugin { - private static final String TRANSFER_SOURCE_CLEARED = "SOURCE_CLEARED"; - private static final String TRANSFER_CLAIMED = "CLAIMED"; - private static final String TRANSFER_PREPARED = "PREPARED"; - private final ProxyServer server; private final Logger logger; private final Path dataDirectory; @@ -56,6 +56,7 @@ public final class ZorwethVelocityPlugin { loadConfig(); this.dataSource = createDataSource(); this.router = new RocketTransferRouter(this.dataSource); + registerCommands(); } @Subscribe @@ -66,48 +67,34 @@ public final class ZorwethVelocityPlugin { } } -// @Subscribe(priority = -1) -// public void onServerPreConnect(final ServerPreConnectEvent event) { -// final PendingTransfer transfer; -// try { -// transfer = this.router.getPendingTransfer(event.getPlayer().getUniqueId()); -// } catch (final SQLException exception) { -// this.logger.error("Failed to look up pending rocket transfer", exception); -// routeToHoldingOrDisconnect(event); -// return; -// } -// -// if (transfer == null || TRANSFER_PREPARED.equals(transfer.state())) { -// return; -// } -// if (!TRANSFER_SOURCE_CLEARED.equals(transfer.state()) && !TRANSFER_CLAIMED.equals(transfer.state())) { -// return; -// } -// -// final Optional destination = this.server.getServer(transfer.destinationServer()); -// if (destination.isEmpty()) { -// routeToHoldingOrDisconnect(event); -// return; -// } -// event.setResult(ServerPreConnectEvent.ServerResult.allowed(destination.get())); -// } -// -// @Subscribe -// public void onServerPostConnect(final ServerPostConnectEvent event) { -// final Optional currentServer = event.getPlayer().getCurrentServer(); -// if (currentServer.isEmpty()) { -// return; -// } -// -// try { -// this.router.updateLastServer( -// event.getPlayer().getUniqueId(), -// currentServer.get().getServerInfo().getName() -// ); -// } catch (final SQLException exception) { -// this.logger.error("Failed to update player last server", exception); -// } -// } + @Subscribe(priority = -1) + public void onServerPreConnect(final ServerPreConnectEvent event) { + if (canBypass(event.getPlayer())) { + return; + } + + final RegisteredServer target = event.getResult().getServer().orElse(event.getOriginalServer()); + final String targetName = target.getServerInfo().getName(); + final String originalName = event.getOriginalServer().getServerInfo().getName(); + if (!isProtectedServer(targetName) && !isProtectedServer(originalName)) { + return; + } + + final String expectedServer; + try { + expectedServer = getExpectedServer(event.getPlayer().getUniqueId()); + } catch (final SQLException exception) { + this.logger.error("Failed to look up rocket player route", exception); + routeToHoldingOrDisconnect(event); + return; + } + + if (targetName.equals(expectedServer)) { + return; + } + + forceExpectedServer(event, expectedServer); + } private void routeToHoldingOrDisconnect(final ServerPreConnectEvent event) { final String holdingServer = this.config.node("holding-server").getString("").trim(); @@ -124,6 +111,69 @@ public final class ZorwethVelocityPlugin { )); } + private void forceExpectedServer(final ServerPreConnectEvent event, final String expectedServer) { + final Optional expected = this.server.getServer(expectedServer); + if (expected.isEmpty()) { + this.logger.error("Configured rocket route server {} does not exist", expectedServer); + routeToHoldingOrDisconnect(event); + return; + } + event.setResult(ServerPreConnectEvent.ServerResult.allowed(expected.get())); + } + + public boolean isProtectedServer(final String serverName) { + return serverName.equals(getMainServer()) || serverName.equals(getZorwethServer()); + } + + public boolean canBypass(final Player player) { + final String permission = this.config.node("admin-bypass-permission").getString("zorweth.admin"); + return !permission.isBlank() && player.hasPermission(permission); + } + + public String getExpectedServer(final UUID playerId) throws SQLException { + final PlayerRoute route = this.router.getPlayerRoute(playerId); + return route == null ? getDefaultServer() : route.expectedServer(); + } + + public void setExpectedServer(final UUID playerId, final String expectedServer) throws SQLException { + if (!isProtectedServer(expectedServer)) { + throw new IllegalArgumentException("Expected server must be " + getMainServer() + " or " + getZorwethServer()); + } + this.router.setPlayerRoute(playerId, expectedServer, "OVERRIDE", null); + } + + public String getDefaultServer() { + return this.config.node("default-server").getString(getMainServer()); + } + + public String getMainServer() { + return this.config.node("main-server").getString("main"); + } + + public String getZorwethServer() { + return this.config.node("zorweth-server").getString("zorweth"); + } + + private void registerCommands() { + final CommandManager commandManager = this.server.getCommandManager(); + final CommandMeta routeMeta = commandManager.metaBuilder("zorwethroute") + .plugin(this) + .build(); + commandManager.register(routeMeta, new RouteCommand()); + } + + private UUID parsePlayerId(final String input) throws SQLException { + try { + return UUID.fromString(input); + } catch (final IllegalArgumentException ignored) { + final Optional onlinePlayer = this.server.getPlayer(input); + if (onlinePlayer.isPresent()) { + return onlinePlayer.get().getUniqueId(); + } + return this.router.getPlayerId(input); + } + } + private void loadConfig() { try { if (!Files.exists(this.dataDirectory)) { @@ -145,6 +195,12 @@ public final class ZorwethVelocityPlugin { } private HikariDataSource createDataSource() { + try { + Class.forName("org.mariadb.jdbc.Driver"); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + final CommentedConfigurationNode database = this.config.node("database"); final HikariConfig hikariConfig = new HikariConfig(); hikariConfig.setJdbcUrl("jdbc:" + database.node("driver").getString("mariadb") + "://" @@ -163,7 +219,60 @@ public final class ZorwethVelocityPlugin { return new HikariDataSource(hikariConfig); } - private record PendingTransfer(String state, String destinationServer) { + private record PlayerRoute(String expectedServer) { + } + + private final class RouteCommand implements SimpleCommand { + + @Override + public void execute(final Invocation invocation) { + final CommandSource source = invocation.source(); + if (!source.hasPermission("zorweth.admin")) { + source.sendMessage(Component.text("You do not have permission to use this command.", NamedTextColor.RED)); + return; + } + final String[] args = invocation.arguments(); + if (args.length != 2) { + source.sendMessage(Component.text("Usage: /zorwethroute ", NamedTextColor.RED)); + return; + } + + final String expectedServer = args[1]; + if (!isProtectedServer(expectedServer)) { + source.sendMessage(Component.text("Server must be " + getMainServer() + " or " + getZorwethServer(), NamedTextColor.RED)); + return; + } + + CompletableFuture.runAsync(() -> { + final UUID playerId; + try { + playerId = parsePlayerId(args[0]); + } catch (final SQLException exception) { + source.sendMessage(Component.text("Failed to look up player.", NamedTextColor.RED)); + logger.error("Failed to look up player for Zorweth route override", exception); + return; + } + + if (playerId == null) { + source.sendMessage(Component.text("Unknown player. Use a UUID for offline players not in NameAPI.", NamedTextColor.RED)); + return; + } + + try { + setExpectedServer(playerId, expectedServer); + } catch (final SQLException exception) { + source.sendMessage(Component.text("Failed to update route.", NamedTextColor.RED)); + logger.error("Failed to override Zorweth route", exception); + return; + } + source.sendMessage(Component.text("Set " + playerId + " expected server to " + expectedServer + ".", NamedTextColor.GREEN)); + }); + } + + @Override + public boolean hasPermission(final Invocation invocation) { + return invocation.source().hasPermission("zorweth.admin"); + } } private static final class RocketTransferRouter { @@ -174,40 +283,60 @@ public final class ZorwethVelocityPlugin { this.dataSource = dataSource; } - private PendingTransfer getPendingTransfer(final UUID playerUuid) throws SQLException { + private PlayerRoute getPlayerRoute(final UUID playerUuid) throws SQLException { try (Connection connection = this.dataSource.getConnection(); - PreparedStatement statement = connection.prepareStatement(""" - SELECT rt.state, rt.destination_server - FROM rocket_transfer_players rtp - JOIN rocket_transfers rt ON rt.transfer_id = rtp.transfer_id - WHERE rtp.player_uuid = ? - AND rt.state IN ('PREPARED', 'SOURCE_CLEARED', 'CLAIMED') - AND rtp.state IN ('PENDING', 'CLAIMED') - ORDER BY rt.created_at DESC - LIMIT 1 - """)) { + PreparedStatement statement = connection.prepareStatement(""" + SELECT expected_server + FROM rocket_player_routes + WHERE player_uuid = ? + """)) { statement.setString(1, playerUuid.toString()); try (ResultSet resultSet = statement.executeQuery()) { if (!resultSet.next()) { return null; } - return new PendingTransfer( - resultSet.getString("state"), - resultSet.getString("destination_server") - ); + return new PlayerRoute(resultSet.getString("expected_server")); } } } - private void updateLastServer(final UUID playerUuid, final String serverName) throws SQLException { + private UUID getPlayerId(final String playerName) throws SQLException { try (Connection connection = this.dataSource.getConnection(); PreparedStatement statement = connection.prepareStatement(""" - INSERT INTO player_server_state (player_uuid, last_server) - VALUES (?, ?) - ON DUPLICATE KEY UPDATE last_server = VALUES(last_server) + SELECT uuid + FROM Name_player + WHERE player = ? + LIMIT 1 + """)) { + statement.setString(1, playerName); + try (ResultSet resultSet = statement.executeQuery()) { + if (!resultSet.next()) { + return null; + } + return UUID.fromString(resultSet.getString("uuid")); + } + } + } + + private void setPlayerRoute(final UUID playerUuid, final String expectedServer, final String source, + final UUID transferId) throws SQLException { + try (Connection connection = this.dataSource.getConnection(); + PreparedStatement statement = connection.prepareStatement(""" + INSERT INTO rocket_player_routes (player_uuid, expected_server, source, transfer_id) + VALUES (?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + expected_server = VALUES(expected_server), + source = VALUES(source), + transfer_id = VALUES(transfer_id) """)) { statement.setString(1, playerUuid.toString()); - statement.setString(2, serverName); + statement.setString(2, expectedServer); + statement.setString(3, source); + if (transferId == null) { + statement.setNull(4, java.sql.Types.VARCHAR); + } else { + statement.setString(4, transferId.toString()); + } statement.executeUpdate(); } } diff --git a/plugins/zorweth-velocity/src/main/resources/config.yml b/plugins/zorweth-velocity/src/main/resources/config.yml index 781dce759..1294c2568 100644 --- a/plugins/zorweth-velocity/src/main/resources/config.yml +++ b/plugins/zorweth-velocity/src/main/resources/config.yml @@ -10,5 +10,8 @@ database: idle_timeout: 600000 max_lifetime: 7200000 -holding-server: "" +holding-server: "pvp" failure-message: "Unable to verify your rocket transfer. Please reconnect and try again." +main-server: main +zorweth-server: zorweth +default-server: main