From d49dc3f24bb8aa8798766b3808d7269cfe040cdf Mon Sep 17 00:00:00 2001 From: Jake Potrebic <jake.m.potrebic@gmail.com> Date: Tue, 25 May 2021 11:20:28 -0700 Subject: [PATCH] Add PlayerKickEvent causes (#5648) --- .../Add-PlayerKickEvent-causes.patch | 127 ++++++ .../Add-PlayerKickEvent-causes.patch | 393 ++++++++++++++++++ 2 files changed, 520 insertions(+) create mode 100644 Spigot-API-Patches/Add-PlayerKickEvent-causes.patch create mode 100644 Spigot-Server-Patches/Add-PlayerKickEvent-causes.patch diff --git a/Spigot-API-Patches/Add-PlayerKickEvent-causes.patch b/Spigot-API-Patches/Add-PlayerKickEvent-causes.patch new file mode 100644 index 0000000000..11a7219daf --- /dev/null +++ b/Spigot-API-Patches/Add-PlayerKickEvent-causes.patch @@ -0,0 +1,127 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic <jake.m.potrebic@gmail.com> +Date: Sat, 15 May 2021 20:30:34 -0700 +Subject: [PATCH] Add PlayerKickEvent causes + + +diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/entity/Player.java ++++ b/src/main/java/org/bukkit/entity/Player.java +@@ -0,0 +0,0 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM + * @param message kick message + */ + void kick(final @Nullable net.kyori.adventure.text.Component message); ++ ++ /** ++ * Kicks player with custom kick message and cause. ++ * ++ * @param message kick message ++ * @param cause kick cause ++ */ ++ void kick(final @Nullable Component message, @NotNull org.bukkit.event.player.PlayerKickEvent.Cause cause); + // Paper end + + /** +diff --git a/src/main/java/org/bukkit/event/player/PlayerKickEvent.java b/src/main/java/org/bukkit/event/player/PlayerKickEvent.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/event/player/PlayerKickEvent.java ++++ b/src/main/java/org/bukkit/event/player/PlayerKickEvent.java +@@ -0,0 +0,0 @@ public class PlayerKickEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private net.kyori.adventure.text.Component leaveMessage; // Paper + private net.kyori.adventure.text.Component kickReason; // Paper ++ private final Cause cause; // Paper + private Boolean cancel; + + @Deprecated // Paper +@@ -0,0 +0,0 @@ public class PlayerKickEvent extends PlayerEvent implements Cancellable { + super(playerKicked); + this.kickReason = org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().deserialize(kickReason); // Paper + this.leaveMessage = org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().deserialize(leaveMessage); // Paper ++ this.cause = Cause.UNKNOWN; // Paper + this.cancel = false; + } + // Paper start ++ @Deprecated + public PlayerKickEvent(@NotNull final Player playerKicked, @NotNull final net.kyori.adventure.text.Component kickReason, @NotNull final net.kyori.adventure.text.Component leaveMessage) { + super(playerKicked); + this.kickReason = kickReason; + this.leaveMessage = leaveMessage; + this.cancel = false; ++ this.cause = Cause.UNKNOWN; ++ } ++ ++ public PlayerKickEvent(@NotNull final Player playerKicked, @NotNull final net.kyori.adventure.text.Component kickReason, @NotNull final net.kyori.adventure.text.Component leaveMessage, @NotNull final Cause cause) { ++ super(playerKicked); ++ this.kickReason = kickReason; ++ this.leaveMessage = leaveMessage; ++ this.cancel = false; ++ this.cause = cause; + } + + /** +@@ -0,0 +0,0 @@ public class PlayerKickEvent extends PlayerEvent implements Cancellable { + public static HandlerList getHandlerList() { + return handlers; + } ++ // Paper start ++ /** ++ * Gets the cause of this kick ++ * ++ * @return ++ */ ++ @NotNull ++ public org.bukkit.event.player.PlayerKickEvent.Cause getCause() { ++ return cause; ++ } ++ ++ public enum Cause { ++ ++ PLUGIN, ++ ++ WHITELIST, ++ ++ BANNED, ++ ++ IP_BANNED, ++ ++ KICK_COMMAND, ++ ++ FLYING_PLAYER, ++ ++ FLYING_VEHICLE, ++ ++ TIMEOUT, ++ ++ IDLING, ++ ++ INVALID_VEHICLE_MOVEMENT, ++ ++ INVALID_PLAYER_MOVEMENT, ++ ++ INVALID_ENTITY_ATTACKED, ++ ++ INVALID_PAYLOAD, ++ ++ SPAM, ++ ++ ILLEGAL_ACTION, ++ ++ ILLEGAL_CHARACTERS, ++ ++ SELF_INTERACTION, ++ ++ DUPLICATE_LOGIN, ++ ++ /** ++ * Spigot's restart command ++ */ ++ RESTART_COMMAND, ++ /** ++ * Fallback cause ++ */ ++ UNKNOWN, ++ } ++ // Paper end + } diff --git a/Spigot-Server-Patches/Add-PlayerKickEvent-causes.patch b/Spigot-Server-Patches/Add-PlayerKickEvent-causes.patch new file mode 100644 index 0000000000..4fe4eaf2c7 --- /dev/null +++ b/Spigot-Server-Patches/Add-PlayerKickEvent-causes.patch @@ -0,0 +1,393 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic <jake.m.potrebic@gmail.com> +Date: Sat, 15 May 2021 20:30:45 -0700 +Subject: [PATCH] Add PlayerKickEvent causes + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas + EntityPlayer entityplayer = (EntityPlayer) iterator.next(); + + if (!whitelist.isWhitelisted(entityplayer.getProfile())) { +- entityplayer.playerConnection.disconnect(org.spigotmc.SpigotConfig.whitelistMessage); // Paper - use configurable message ++ entityplayer.playerConnection.disconnect(org.spigotmc.SpigotConfig.whitelistMessage, org.bukkit.event.player.PlayerKickEvent.Cause.WHITELIST); // Paper - use configurable message + } + } + +diff --git a/src/main/java/net/minecraft/server/commands/CommandBan.java b/src/main/java/net/minecraft/server/commands/CommandBan.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/commands/CommandBan.java ++++ b/src/main/java/net/minecraft/server/commands/CommandBan.java +@@ -0,0 +0,0 @@ public class CommandBan { + EntityPlayer entityplayer = commandlistenerwrapper.getServer().getPlayerList().getPlayer(gameprofile.getId()); + + if (entityplayer != null) { +- entityplayer.playerConnection.disconnect(new ChatMessage("multiplayer.disconnect.banned")); ++ entityplayer.playerConnection.disconnect(new ChatMessage("multiplayer.disconnect.banned"), org.bukkit.event.player.PlayerKickEvent.Cause.BANNED); // Paper - kick event cause + } + } + } +diff --git a/src/main/java/net/minecraft/server/commands/CommandBanIp.java b/src/main/java/net/minecraft/server/commands/CommandBanIp.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/commands/CommandBanIp.java ++++ b/src/main/java/net/minecraft/server/commands/CommandBanIp.java +@@ -0,0 +0,0 @@ public class CommandBanIp { + while (iterator.hasNext()) { + EntityPlayer entityplayer = (EntityPlayer) iterator.next(); + +- entityplayer.playerConnection.disconnect(new ChatMessage("multiplayer.disconnect.ip_banned")); ++ entityplayer.playerConnection.disconnect(new ChatMessage("multiplayer.disconnect.ip_banned"), org.bukkit.event.player.PlayerKickEvent.Cause.IP_BANNED); // Paper - kick event cause + } + + return list.size(); +diff --git a/src/main/java/net/minecraft/server/commands/CommandKick.java b/src/main/java/net/minecraft/server/commands/CommandKick.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/commands/CommandKick.java ++++ b/src/main/java/net/minecraft/server/commands/CommandKick.java +@@ -0,0 +0,0 @@ public class CommandKick { + while (iterator.hasNext()) { + EntityPlayer entityplayer = (EntityPlayer) iterator.next(); + +- entityplayer.playerConnection.disconnect(ichatbasecomponent); ++ entityplayer.playerConnection.disconnect(ichatbasecomponent, org.bukkit.event.player.PlayerKickEvent.Cause.KICK_COMMAND); // Paper - kick event cause + commandlistenerwrapper.sendMessage(new ChatMessage("commands.kick.success", new Object[]{entityplayer.getScoreboardDisplayName(), ichatbasecomponent}), true); + } + +diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java +@@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn { + if (this.B && !this.player.isSleeping()) { + if (++this.C > 80) { + PlayerConnection.LOGGER.warn("{} was kicked for floating too long!", this.player.getDisplayName().getString()); +- this.disconnect(com.destroystokyo.paper.PaperConfig.flyingKickPlayerMessage); // Paper - use configurable kick message ++ this.disconnect(com.destroystokyo.paper.PaperConfig.flyingKickPlayerMessage, org.bukkit.event.player.PlayerKickEvent.Cause.FLYING_PLAYER); // Paper - use configurable kick message & kick event cause + return; + } + } else { +@@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn { + if (this.D && this.player.getRootVehicle().getRidingPassenger() == this.player) { + if (++this.E > 80) { + PlayerConnection.LOGGER.warn("{} was kicked for floating a vehicle too long!", this.player.getDisplayName().getString()); +- this.disconnect(com.destroystokyo.paper.PaperConfig.flyingKickVehicleMessage); // Paper - use configurable kick message ++ this.disconnect(com.destroystokyo.paper.PaperConfig.flyingKickVehicleMessage, org.bukkit.event.player.PlayerKickEvent.Cause.FLYING_VEHICLE); // Paper - use configurable kick message & kick event cause + return; + } + } else { +@@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn { + if (this.isPendingPing()) { + if (!this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // check keepalive limit, don't fire if already disconnected + PlayerConnection.LOGGER.warn("{} was kicked due to keepalive timeout!", this.player.getName()); // more info +- this.disconnect(new ChatMessage("disconnect.timeout", new Object[0])); ++ this.disconnect(new ChatMessage("disconnect.timeout", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause + } + } else { + if (elapsedTime >= 15000L) { // 15 seconds +@@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn { + + if (this.player.F() > 0L && this.minecraftServer.getIdleTimeout() > 0 && SystemUtils.getMonotonicMillis() - this.player.F() > (long) (this.minecraftServer.getIdleTimeout() * 1000 * 60)) { + this.player.resetIdleTimer(); // CraftBukkit - SPIGOT-854 +- this.disconnect(new ChatMessage("multiplayer.disconnect.idling")); ++ this.disconnect(new ChatMessage("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause + } + + } +@@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn { + + public void disconnect(String s) { + // Paper start +- this.disconnect(PaperAdventure.LEGACY_SECTION_UXRC.deserialize(s)); ++ this.disconnect(PaperAdventure.LEGACY_SECTION_UXRC.deserialize(s), org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); ++ } ++ ++ public void disconnect(String s, PlayerKickEvent.Cause cause) { ++ this.disconnect(PaperAdventure.LEGACY_SECTION_UXRC.deserialize(s), cause); + } + + public void disconnect(final IChatBaseComponent reason) { +- this.disconnect(PaperAdventure.asAdventure(reason)); ++ this.disconnect(PaperAdventure.asAdventure(reason), org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); ++ } ++ ++ public void disconnect(final IChatBaseComponent reason, PlayerKickEvent.Cause cause) { ++ this.disconnect(PaperAdventure.asAdventure(reason), cause); + } + +- public void disconnect(net.kyori.adventure.text.Component reason) { ++ public void disconnect(net.kyori.adventure.text.Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) { + // Paper end + // CraftBukkit start - fire PlayerKickEvent + if (this.processedDisconnect) { +@@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn { + } + net.kyori.adventure.text.Component leaveMessage = net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, this.player.getBukkitEntity().displayName()); // Paper - Adventure + +- PlayerKickEvent event = new PlayerKickEvent(this.server.getPlayer(this.player), reason, leaveMessage); // Paper - Adventure ++ PlayerKickEvent event = new PlayerKickEvent(this.server.getPlayer(this.player), reason, leaveMessage, cause); // Paper - Adventure & kick event reason + + if (this.server.getServer().isRunning()) { + this.server.getPluginManager().callEvent(event); +@@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn { + public void a(PacketPlayInVehicleMove packetplayinvehiclemove) { + PlayerConnectionUtils.ensureMainThread(packetplayinvehiclemove, this, this.player.getWorldServer()); + if (b(packetplayinvehiclemove)) { +- this.disconnect(new ChatMessage("multiplayer.disconnect.invalid_vehicle_movement")); ++ this.disconnect(new ChatMessage("multiplayer.disconnect.invalid_vehicle_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_VEHICLE_MOVEMENT); // Paper - kick event cause + } else { + Entity entity = this.player.getRootVehicle(); + +@@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn { + // PlayerConnectionUtils.ensureMainThread(packetplayintabcomplete, this, this.player.getWorldServer()); // Paper - run this async + // CraftBukkit start + if (tabSpamLimiter.addAndGet(com.destroystokyo.paper.PaperConfig.tabSpamIncrement) > com.destroystokyo.paper.PaperConfig.tabSpamLimit && !this.minecraftServer.getPlayerList().isOp(this.player.getProfile())) { // Paper start - split and make configurable +- minecraftServer.scheduleOnMain(() -> this.disconnect(new ChatMessage("disconnect.spam", new Object[0]))); // Paper ++ minecraftServer.scheduleOnMain(() -> this.disconnect(new ChatMessage("disconnect.spam", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM)); // Paper - kick event cause + return; + } + // Paper start + String str = packetplayintabcomplete.c(); int index = -1; + if (str.length() > 64 && ((index = str.indexOf(' ')) == -1 || index >= 64)) { +- minecraftServer.scheduleOnMain(() -> this.disconnect(new ChatMessage("disconnect.spam", new Object[0]))); // Paper ++ minecraftServer.scheduleOnMain(() -> this.disconnect(new ChatMessage("disconnect.spam", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM)); // Paper - kick event cause + return; + } + // Paper end +@@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn { + // Paper start - validate pick item position + if (!(packetplayinpickitem.b() >= 0 && packetplayinpickitem.b() < this.player.inventory.items.size())) { + PlayerConnection.LOGGER.warn("{} tried to set an invalid carried item", this.player.getDisplayName().getString()); +- this.disconnect("Invalid hotbar selection (Hacking?)"); ++ this.disconnect("Invalid hotbar selection (Hacking?)", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause + return; + } + this.player.inventory.c(packetplayinpickitem.b()); // Paper - Diff above if changed +@@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn { + NBTTagList pageList = testStack.getTag().getList("pages", 8); + if (pageList.size() > 100) { + PlayerConnection.LOGGER.warn(this.player.getName() + " tried to send a book with too many pages"); +- minecraftServer.scheduleOnMain(() -> this.disconnect("Book too large!")); ++ minecraftServer.scheduleOnMain(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause + return; + } + long byteTotal = 0; +@@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn { + int byteLength = testString.getBytes(java.nio.charset.StandardCharsets.UTF_8).length; + if (byteLength > 256 * 4) { + PlayerConnection.LOGGER.warn(this.player.getName() + " tried to send a book with with a page too large!"); +- minecraftServer.scheduleOnMain(() -> this.disconnect("Book too large!")); ++ minecraftServer.scheduleOnMain(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause + return; + } + byteTotal += byteLength; +@@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn { + + if (byteTotal > byteAllowed) { + PlayerConnection.LOGGER.warn(this.player.getName() + " tried to send too large of a book. Book Size: " + byteTotal + " - Allowed: "+ byteAllowed + " - Pages: " + pageList.size()); +- minecraftServer.scheduleOnMain(() -> this.disconnect("Book too large!")); ++ minecraftServer.scheduleOnMain(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause + return; + } + } + // Paper end + // CraftBukkit start + if (this.lastBookTick + 20 > MinecraftServer.currentTick) { +- this.disconnect("Book edited too quickly!"); ++ this.disconnect("Book edited too quickly!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause + return; + } + this.lastBookTick = MinecraftServer.currentTick; +@@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn { + public void a(PacketPlayInFlying packetplayinflying) { + PlayerConnectionUtils.ensureMainThread(packetplayinflying, this, this.player.getWorldServer()); + if (b(packetplayinflying)) { +- this.disconnect(new ChatMessage("multiplayer.disconnect.invalid_player_movement")); ++ this.disconnect(new ChatMessage("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause + } else { + WorldServer worldserver = this.player.getWorldServer(); + +@@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn { + this.dropCount++; + if (this.dropCount >= 20) { + LOGGER.warn(this.player.getName() + " dropped their items too quickly!"); +- this.disconnect("You dropped your items too quickly (Hacking?)"); ++ this.disconnect("You dropped your items too quickly (Hacking?)", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause + return; + } + } +@@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn { + this.player.resetIdleTimer(); + } else { + PlayerConnection.LOGGER.warn("{} tried to set an invalid carried item", this.player.getDisplayName().getString()); +- this.disconnect("Invalid hotbar selection (Hacking?)"); // CraftBukkit ++ this.disconnect("Invalid hotbar selection (Hacking?)", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // CraftBukkit // Paper - kick event cause + } + } + +@@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn { + Waitable waitable = new Waitable() { + @Override + protected Object evaluate() { +- PlayerConnection.this.disconnect(new ChatMessage("multiplayer.disconnect.illegal_characters")); ++ PlayerConnection.this.disconnect(new ChatMessage("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper - kick event cause + return null; + } + }; +@@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn { + throw new RuntimeException(e); + } + } else { +- this.disconnect(new ChatMessage("multiplayer.disconnect.illegal_characters")); ++ this.disconnect(new ChatMessage("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper - kick event cause + } + // CraftBukkit end + return; +@@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn { + Waitable waitable = new Waitable() { + @Override + protected Object evaluate() { +- PlayerConnection.this.disconnect(new ChatMessage("disconnect.spam")); ++ PlayerConnection.this.disconnect(new ChatMessage("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - kick event cause + return null; + } + }; +@@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn { + throw new RuntimeException(e); + } + } else { +- this.disconnect(new ChatMessage("disconnect.spam")); ++ this.disconnect(new ChatMessage("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - kick event cause + } + // CraftBukkit end + } +@@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn { + // Spigot Start + if ( entity == player && !player.isSpectator() ) + { +- disconnect( "Cannot interact with self!" ); ++ disconnect( "Cannot interact with self!", org.bukkit.event.player.PlayerKickEvent.Cause.SELF_INTERACTION ); // Paper - kick event cause + return; + } + // Spigot End +@@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn { + // CraftBukkit end + } else if (packetplayinuseentity.b() == PacketPlayInUseEntity.EnumEntityUseAction.ATTACK) { + if (entity instanceof EntityItem || entity instanceof EntityExperienceOrb || entity instanceof EntityArrow || (entity == this.player && !player.isSpectator())) { // CraftBukkit +- this.disconnect(new ChatMessage("multiplayer.disconnect.invalid_entity_attacked")); ++ this.disconnect(new ChatMessage("multiplayer.disconnect.invalid_entity_attacked"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_ENTITY_ATTACKED); // Paper - kick event cause + PlayerConnection.LOGGER.warn("Player {} tried to attack an invalid entity", this.player.getDisplayName().getString()); + return; + } +@@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn { + // Paper start + if (!Bukkit.isPrimaryThread()) { + if (recipeSpamPackets.addAndGet(PaperConfig.autoRecipeIncrement) > PaperConfig.autoRecipeLimit) { +- minecraftServer.scheduleOnMain(() -> this.disconnect(new ChatMessage("disconnect.spam", new Object[0]))); // Paper ++ minecraftServer.scheduleOnMain(() -> this.disconnect(new ChatMessage("disconnect.spam", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM)); // Paper - kick event cause + return; + } + } +@@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn { + } else if (!this.isExemptPlayer()) { + // Paper start - This needs to be handled on the main thread for plugins + minecraftServer.scheduleOnMain(() -> { +- this.disconnect(new ChatMessage("disconnect.timeout")); ++ this.disconnect(new ChatMessage("disconnect.timeout"), org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause + }); + // Paper end + } +@@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn { + } + } catch (Exception ex) { + PlayerConnection.LOGGER.error("Couldn\'t register custom payload", ex); +- this.disconnect("Invalid payload REGISTER!"); ++ this.disconnect("Invalid payload REGISTER!", org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause + } + } else if (packetplayincustompayload.tag.equals(CUSTOM_UNREGISTER)) { + try { +@@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn { + } + } catch (Exception ex) { + PlayerConnection.LOGGER.error("Couldn\'t unregister custom payload", ex); +- this.disconnect("Invalid payload UNREGISTER!"); ++ this.disconnect("Invalid payload UNREGISTER!", org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause + } + } else { + try { +@@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn { + server.getMessenger().dispatchIncomingMessage(player.getBukkitEntity(), packetplayincustompayload.tag.toString(), data); + } catch (Exception ex) { + PlayerConnection.LOGGER.error("Couldn\'t dispatch custom payload", ex); +- this.disconnect("Invalid custom payload!"); ++ this.disconnect("Invalid custom payload!", org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause + } + } + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -0,0 +0,0 @@ public abstract class PlayerList { + while (iterator.hasNext()) { + entityplayer = (EntityPlayer) iterator.next(); + savePlayerFile(entityplayer); // CraftBukkit - Force the player's inventory to be saved +- entityplayer.playerConnection.disconnect(new ChatMessage("multiplayer.disconnect.duplicate_login", new Object[0])); ++ entityplayer.playerConnection.disconnect(new ChatMessage("multiplayer.disconnect.duplicate_login", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.DUPLICATE_LOGIN); // Paper - kick event cause + } + + // Instead of kicking then returning, we need to store the kick reason +@@ -0,0 +0,0 @@ public abstract class PlayerList { + public void shutdown(boolean isRestarting) { + // CraftBukkit start - disconnect safely + for (EntityPlayer player : this.players) { +- if (isRestarting) player.playerConnection.disconnect(org.spigotmc.SpigotConfig.restartMessage); else // Paper +- player.playerConnection.disconnect(this.server.server.shutdownMessage()); // CraftBukkit - add custom shutdown message // Paper - Adventure ++ if (isRestarting) player.playerConnection.disconnect(org.spigotmc.SpigotConfig.restartMessage, org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); else // Paper - kick event cause (cause is never used here) ++ player.playerConnection.disconnect(this.server.server.shutdownMessage(), org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); // CraftBukkit - add custom shutdown message // Paper - Adventure & KickEventCause (cause is never used here) + } + // CraftBukkit end + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -0,0 +0,0 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + org.spigotmc.AsyncCatcher.catchOp("player kick"); // Spigot + if (getHandle().playerConnection == null) return; + +- getHandle().playerConnection.disconnect(message == null ? "" : message); ++ getHandle().playerConnection.disconnect(message == null ? "" : message, org.bukkit.event.player.PlayerKickEvent.Cause.PLUGIN); // Paper - kick event cause + } + + // Paper start + @Override + public void kick(final net.kyori.adventure.text.Component message) { ++ kick(message, org.bukkit.event.player.PlayerKickEvent.Cause.PLUGIN); ++ } ++ ++ @Override ++ public void kick(net.kyori.adventure.text.Component message, org.bukkit.event.player.PlayerKickEvent.Cause cause) { + org.spigotmc.AsyncCatcher.catchOp("player kick"); + final PlayerConnection connection = this.getHandle().playerConnection; + if (connection != null) { +- connection.disconnect(message == null ? net.kyori.adventure.text.Component.empty() : message); ++ connection.disconnect(message == null ? net.kyori.adventure.text.Component.empty() : message, cause); + } + } + // Paper end +diff --git a/src/main/java/org/spigotmc/RestartCommand.java b/src/main/java/org/spigotmc/RestartCommand.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/spigotmc/RestartCommand.java ++++ b/src/main/java/org/spigotmc/RestartCommand.java +@@ -0,0 +0,0 @@ public class RestartCommand extends Command + // Kick all players + for ( EntityPlayer p : com.google.common.collect.ImmutableList.copyOf( MinecraftServer.getServer().getPlayerList().players ) ) + { +- p.playerConnection.disconnect(SpigotConfig.restartMessage); ++ p.playerConnection.disconnect(SpigotConfig.restartMessage, org.bukkit.event.player.PlayerKickEvent.Cause.RESTART_COMMAND); // Paper - kick event reason (cause is never used)) + } + // Give the socket a chance to send the packets + try