diff --git a/src/main/java/pw/kaboom/extras/modules/player/PlayerConnection.java b/src/main/java/pw/kaboom/extras/modules/player/PlayerConnection.java index b2be174..5e8a69b 100644 --- a/src/main/java/pw/kaboom/extras/modules/player/PlayerConnection.java +++ b/src/main/java/pw/kaboom/extras/modules/player/PlayerConnection.java @@ -1,21 +1,26 @@ package pw.kaboom.extras.modules.player; import com.destroystokyo.paper.event.profile.PreLookupProfileEvent; +import com.destroystokyo.paper.profile.PlayerProfile; import com.google.common.base.Charsets; import io.papermc.paper.event.player.AsyncPlayerSpawnLocationEvent; +import io.papermc.paper.event.connection.PlayerConnectionValidateLoginEvent; +import io.papermc.paper.event.player.PlayerServerFullCheckEvent; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.title.Title; import org.bukkit.Bukkit; import org.bukkit.Location; -import org.bukkit.Server; import org.bukkit.World; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; -import org.bukkit.event.player.*; -import org.bukkit.event.player.PlayerLoginEvent.Result; +import org.bukkit.event.player.AsyncPlayerPreLoginEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerKickEvent; +import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.plugin.java.JavaPlugin; import pw.kaboom.extras.Main; import pw.kaboom.extras.modules.server.ServerTabComplete; @@ -23,8 +28,7 @@ import pw.kaboom.extras.util.Utility; import java.time.Duration; -import java.util.EnumSet; -import java.util.UUID; +import java.util.*; import java.util.concurrent.ThreadLocalRandom; public final class PlayerConnection implements Listener { @@ -75,6 +79,8 @@ public final class PlayerConnection implements Listener { PlayerKickEvent.Cause.TOO_MANY_PENDING_CHATS ); + private final Set disallowedLogins = Collections.newSetFromMap(new WeakHashMap<>()); + @EventHandler void onAsyncPlayerPreLogin(final AsyncPlayerPreLoginEvent event) { final Player player = Utility.getPlayerExactIgnoreCase(event.getName()); @@ -83,25 +89,16 @@ void onAsyncPlayerPreLogin(final AsyncPlayerPreLoginEvent event) { event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, Component.text("A player with that username is already logged in")); } - - /*try { - final PlayerProfile profile = event.getPlayerProfile(); - - UUID offlineUUID = UUID.nameUUIDFromBytes( - ("OfflinePlayer:" + event.getName()).getBytes(Charsets.UTF_8)); - - profile.setId(offlineUUID); - - SkinDownloader skinDownloader = new SkinDownloader(); - skinDownloader.fillJoinProfile(profile, event.getName(), event.getUniqueId()); - } catch (Exception ignored) { - }*/ } @EventHandler void onPlayerJoin(final PlayerJoinEvent event) { final Player player = event.getPlayer(); + if (OP_ON_JOIN && !player.isOp()) { + player.setOp(true); + } + player.showTitle(Title.title( TITLE, SUBTITLE, @@ -109,6 +106,10 @@ void onPlayerJoin(final PlayerJoinEvent event) { )); ServerTabComplete.getLoginNameList().put(player.getUniqueId(), player.getName()); + + if (!player.getPlayerProfile().hasTextures()) { + SkinManager.applySkin(player, player.getName(), false); + } } @EventHandler(ignoreCancelled = true) @@ -122,35 +123,40 @@ void onPlayerKick(final PlayerKickEvent event) { event.setCancelled(true); } - @EventHandler - void onPlayerLogin(final PlayerLoginEvent event) { - // #312 - If allow join on full server is off, - // but join restrictions are disabled, - // player can still join on full server - - // Full server kicks should be handled differently from other join restrictions - // since we have a separate configuration value for it - - if (!ENABLE_JOIN_RESTRICTIONS && !Result.KICK_FULL.equals(event.getResult())) { - event.allow(); - } + @EventHandler(priority = EventPriority.HIGHEST) + void onPlayerServerFullCheck(final PlayerServerFullCheckEvent event) { + if (event.isAllowed()) return; - if (Result.KICK_FULL.equals(event.getResult()) && ALLOW_JOIN_ON_FULL_SERVER) { - event.allow(); + if (ALLOW_JOIN_ON_FULL_SERVER) { + event.allow(true); + return; } - final Player player = event.getPlayer(); + // #312 - If allow join on full server is off, but join restrictions are disabled, player + // can still join on full server - if (OP_ON_JOIN && !player.isOp()) { - player.setOp(true); + // Full server kicks should be handled differently from other join restrictions since we + // have a separate configuration value for it + if (!ENABLE_JOIN_RESTRICTIONS) { + this.disallowedLogins.add(event.getPlayerProfile().getId()); } + } - final Server server = Bukkit.getServer(); + // Note that this event gets fired even if FullCheckEvent returns disallowed. + @SuppressWarnings("UnstableApiUsage") + @EventHandler(priority = EventPriority.HIGHEST) + void onPlayerConnectionValidate(final PlayerConnectionValidateLoginEvent event) { + final PlayerProfile profile = Utility.getConnectionUuid(event.getConnection()); + final Player player = Utility.getPlayerExactIgnoreCase(profile.getName()); + if (player != null) { + event.kickMessage(Component.text("A player with that username is already logged in")); + return; + } + if (ENABLE_JOIN_RESTRICTIONS) return; - if (!server.getOnlineMode()) { - SkinManager.applySkin(player, player.getName(), false); - } + final boolean disallowed = this.disallowedLogins.remove(profile.getId()); + if (!disallowed) event.allow(); } @EventHandler diff --git a/src/main/java/pw/kaboom/extras/modules/player/PlayerPrefix.java b/src/main/java/pw/kaboom/extras/modules/player/PlayerPrefix.java index efae707..da7d936 100644 --- a/src/main/java/pw/kaboom/extras/modules/player/PlayerPrefix.java +++ b/src/main/java/pw/kaboom/extras/modules/player/PlayerPrefix.java @@ -9,7 +9,7 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerLoginEvent; +import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.scheduler.BukkitScheduler; @@ -110,7 +110,7 @@ private static void onUpdate(Player player) throws IOException { } @EventHandler(priority = EventPriority.MONITOR) - public void onPlayerLoginEvent(PlayerLoginEvent event) throws IOException { + public void onPlayerJoinEvent(PlayerJoinEvent event) throws IOException { final Player player = event.getPlayer(); final boolean isOp = player.isOp(); diff --git a/src/main/java/pw/kaboom/extras/util/Utility.java b/src/main/java/pw/kaboom/extras/util/Utility.java index 5c3c164..7bbbe56 100644 --- a/src/main/java/pw/kaboom/extras/util/Utility.java +++ b/src/main/java/pw/kaboom/extras/util/Utility.java @@ -1,5 +1,9 @@ package pw.kaboom.extras.util; +import com.destroystokyo.paper.profile.PlayerProfile; +import io.papermc.paper.connection.PlayerConfigurationConnection; +import io.papermc.paper.connection.PlayerConnection; +import io.papermc.paper.connection.PlayerLoginConnection; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.World; @@ -10,6 +14,7 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.player.PlayerTeleportEvent; +import org.jetbrains.annotations.NotNull; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -17,6 +22,22 @@ import java.util.function.Function; public final class Utility { + @SuppressWarnings("UnstableApiUsage") + public static @NotNull PlayerProfile getConnectionUuid(final PlayerConnection conn) { + final PlayerProfile profile; + + if ((conn instanceof final PlayerLoginConnection login)) { + profile = login.getAuthenticatedProfile(); // This is present even in offline-mode. + } else if ((conn instanceof final PlayerConfigurationConnection config)) { + profile = config.getProfile(); + } else { + throw new IllegalStateException("Invalid phase"); + } + + if (profile == null) throw new IllegalStateException("Connection has no profile"); + return profile; + } + public static void teleportToSpawn(final Player player, final PlayerTeleportEvent.TeleportCause cause) { final World world = player.getServer().getRespawnWorld();