diff --git a/core/src/main/java/com/volmit/iris/Iris.java b/core/src/main/java/com/volmit/iris/Iris.java index a4215738a..65344377d 100644 --- a/core/src/main/java/com/volmit/iris/Iris.java +++ b/core/src/main/java/com/volmit/iris/Iris.java @@ -855,6 +855,7 @@ public class Iris extends VolmitPlugin implements Listener { if (!INMS.get().registerDimension(worldName, dim)) { throw new IllegalStateException("Unable to register dimension " + dim.getName()); } + INMS.get().reconnectAll(); return new BukkitChunkGenerator(w, false, ff, dim.getLoadKey()); } diff --git a/core/src/main/java/com/volmit/iris/core/nms/INMSBinding.java b/core/src/main/java/com/volmit/iris/core/nms/INMSBinding.java index 0ae69bc2b..cf3697786 100644 --- a/core/src/main/java/com/volmit/iris/core/nms/INMSBinding.java +++ b/core/src/main/java/com/volmit/iris/core/nms/INMSBinding.java @@ -36,6 +36,7 @@ import org.bukkit.block.Biome; import org.bukkit.entity.Dolphin; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.generator.ChunkGenerator; import org.bukkit.generator.structure.Structure; @@ -43,6 +44,8 @@ import org.bukkit.inventory.ItemStack; import java.io.File; import java.awt.Color; +import java.util.ArrayList; +import java.util.Collection; public interface INMSBinding { boolean hasTile(Material material); @@ -147,4 +150,11 @@ public interface INMSBinding { IPackRepository getPackRepository(); KList getStructureKeys(); + + default void reconnectAll() { + new ArrayList<>(Bukkit.getOnlinePlayers()) + .forEach(this::reconnect); + } + + void reconnect(Player player); } diff --git a/core/src/main/java/com/volmit/iris/core/nms/v1X/NMSBinding1X.java b/core/src/main/java/com/volmit/iris/core/nms/v1X/NMSBinding1X.java index 873e169a2..539a13588 100644 --- a/core/src/main/java/com/volmit/iris/core/nms/v1X/NMSBinding1X.java +++ b/core/src/main/java/com/volmit/iris/core/nms/v1X/NMSBinding1X.java @@ -45,6 +45,7 @@ import org.bukkit.block.Biome; import org.bukkit.entity.Dolphin; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.generator.ChunkGenerator; import org.bukkit.generator.structure.Structure; @@ -145,6 +146,11 @@ public class NMSBinding1X implements INMSBinding { return new KList<>(list); } + @Override + public void reconnect(Player player) { + + } + @Override public CompoundTag serializeEntity(Entity location) { return null; diff --git a/core/src/main/java/com/volmit/iris/core/tools/IrisWorldCreator.java b/core/src/main/java/com/volmit/iris/core/tools/IrisWorldCreator.java index b21c00237..a210f81f3 100644 --- a/core/src/main/java/com/volmit/iris/core/tools/IrisWorldCreator.java +++ b/core/src/main/java/com/volmit/iris/core/tools/IrisWorldCreator.java @@ -83,6 +83,7 @@ public class IrisWorldCreator { if (!INMS.get().registerDimension(name, dim)) { throw new IllegalStateException("Unable to register dimension " + dim.getName()); } + INMS.get().reconnectAll(); return new WorldCreator(name) .environment(findEnvironment()) diff --git a/nms/v1_19_R1/src/main/java/com/volmit/iris/core/nms/v1_19_R1/NMSBinding.java b/nms/v1_19_R1/src/main/java/com/volmit/iris/core/nms/v1_19_R1/NMSBinding.java index a27f64c94..5f8591108 100644 --- a/nms/v1_19_R1/src/main/java/com/volmit/iris/core/nms/v1_19_R1/NMSBinding.java +++ b/nms/v1_19_R1/src/main/java/com/volmit/iris/core/nms/v1_19_R1/NMSBinding.java @@ -23,11 +23,7 @@ import java.io.*; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Vector; +import java.util.*; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Logger; @@ -40,17 +36,23 @@ import com.volmit.iris.engine.object.IrisBiomeReplacement; import com.volmit.iris.util.scheduling.J; import net.minecraft.nbt.*; import net.minecraft.nbt.Tag; +import net.minecraft.network.Connection; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket; import net.minecraft.resources.RegistryOps; +import net.minecraft.server.PlayerAdvancements; import net.minecraft.server.commands.data.BlockDataAccessor; import com.volmit.iris.core.nms.container.IPackRepository; import com.volmit.iris.util.io.IO; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerLoginPacketListenerImpl; +import net.minecraft.server.players.PlayerList; +import net.minecraft.stats.ServerStatsCounter; +import net.minecraft.stats.Stats; import net.minecraft.tags.TagKey; import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.block.EntityBlock; import com.google.common.base.Preconditions; import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.mojang.datafixers.util.Pair; import com.mojang.serialization.Codec; import com.mojang.serialization.JsonOps; import com.mojang.serialization.Lifecycle; @@ -82,11 +84,13 @@ import org.bukkit.craftbukkit.v1_19_R1.block.CraftBlock; import org.bukkit.craftbukkit.v1_19_R1.block.CraftBlockState; import org.bukkit.craftbukkit.v1_19_R1.block.CraftBlockStates; import org.bukkit.craftbukkit.v1_19_R1.block.data.CraftBlockData; -import org.bukkit.craftbukkit.v1_19_R1.entity.CraftDolphin; +import org.bukkit.craftbukkit.v1_19_R1.entity.CraftPlayer; import org.bukkit.craftbukkit.v1_19_R1.inventory.CraftItemStack; -import org.bukkit.entity.Dolphin; +import org.bukkit.craftbukkit.v1_19_R1.scoreboard.CraftScoreboardManager; import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; @@ -105,7 +109,6 @@ import com.volmit.iris.util.json.JSONObject; import com.volmit.iris.util.mantle.Mantle; import com.volmit.iris.util.math.Vector3d; import com.volmit.iris.util.matter.MatterBiomeInject; -import com.volmit.iris.util.nbt.io.NBTUtil; import com.volmit.iris.util.nbt.mca.NBTWorld; import com.volmit.iris.util.nbt.mca.palette.*; import com.volmit.iris.util.nbt.tag.CompoundTag; @@ -118,13 +121,11 @@ import net.minecraft.core.RegistryAccess; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.EntityType; -import net.minecraft.world.level.biome.BiomeSource; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.LevelChunk; -import sun.misc.Unsafe; public class NMSBinding implements INMSBinding { private final KMap baseBiomeCache = new KMap<>(); @@ -891,6 +892,99 @@ public class NMSBinding implements INMSBinding { return keys; } + @Override + public void reconnect(Player player) { + var serverPlayer = ((CraftPlayer) player).getHandle(); + var listener = serverPlayer.connection; + + try { + var field = getField(listener.getClass(), Connection.class); + field.setAccessible(true); + var connection = (Connection) field.get(listener); + var server = serverPlayer.getServer(); + var playerList = server.getPlayerList(); + J.s(() -> { + try { + remove(serverPlayer); + } catch (Throwable e) { + Iris.error("Failed to remove player " + player.getName()); + e.printStackTrace(); + return; + } + var result = playerList.canPlayerLogin(new ServerLoginPacketListenerImpl(server, connection), serverPlayer.getGameProfile(), serverPlayer.getProfilePublicKey()); + if (result != null) { + playerList.placeNewPlayer(connection, result); + } + }); + } catch (Throwable e) { + Iris.error("Failed to reconnect player " + player.getName()); + e.printStackTrace(); + } + } + + private void remove(ServerPlayer player) throws NoSuchFieldException, IllegalAccessException { + ServerLevel level = player.getLevel(); + player.awardStat(Stats.LEAVE_GAME); + if (player.containerMenu != player.inventoryMenu) { + player.closeContainer(); + } + + PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(player.getBukkitEntity(), "§e" + player.getScoreboardName() + " left the game"); + Bukkit.getPluginManager().callEvent(playerQuitEvent); + player.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); + player.doTick(); + + level.getServer().getPlayerList().playerIo.save(player); + ServerStatsCounter stats = player.getStats(); + if (stats != null) stats.save(); + PlayerAdvancements advancements = player.getAdvancements(); + if (advancements != null) advancements.save(); + + if (player.isPassenger()) { + var vehicle = player.getRootVehicle(); + if (vehicle.hasExactlyOnePlayerPassenger()) { + Iris.debug("Removing player mount"); + player.stopRiding(); + vehicle.getPassengersAndSelf().forEach(passenger -> passenger.setRemoved(net.minecraft.world.entity.Entity.RemovalReason.UNLOADED_WITH_PLAYER)); + } + } + + player.unRide(); + level.removePlayerImmediately(player, net.minecraft.world.entity.Entity.RemovalReason.UNLOADED_WITH_PLAYER); + player.getAdvancements().stopListening(); + + var playersField = getField(PlayerList.class, buildType(List.class, ServerPlayer.class.getName())); + playersField.setAccessible(true); + var playersByNameField = getField(PlayerList.class, buildType(Map.class, String.class.getName(), ServerPlayer.class.getName())); + playersByNameField.setAccessible(true); + var playersByUUIDField = getField(PlayerList.class, buildType(Map.class, UUID.class.getName(), ServerPlayer.class.getName())); + playersByUUIDField.setAccessible(true); + + var players = (List)playersField.get(player.getServer().getPlayerList()); + var playersByName = (Map)playersByNameField.get(player.getServer().getPlayerList()); + var playersByUUID = (Map)playersByUUIDField.get(player.getServer().getPlayerList()); + + players.remove(player); + playersByName.remove(player.getScoreboardName().toLowerCase(Locale.ROOT)); + level.getServer().getCustomBossEvents().onPlayerDisconnect(player); + UUID uuid = player.getUUID(); + ServerPlayer currentPlayer = playersByUUID.get(uuid); + if (currentPlayer == player) { + playersByUUID.remove(uuid); + } + + ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.REMOVE_PLAYER, player); + for (ServerPlayer target : players) { + if (target.getBukkitEntity().canSee(player.getBukkitEntity())) { + target.connection.send(packet); + } else { + target.getBukkitEntity().onEntityRemove(player); + } + } + + ((CraftScoreboardManager) Bukkit.getScoreboardManager()).removePlayer(player.getBukkitEntity()); + } + private static Field getField(Class clazz, Class fieldType) throws NoSuchFieldException { try { for (Field f : clazz.getDeclaredFields()) { diff --git a/nms/v1_19_R2/src/main/java/com/volmit/iris/core/nms/v1_19_R2/NMSBinding.java b/nms/v1_19_R2/src/main/java/com/volmit/iris/core/nms/v1_19_R2/NMSBinding.java index 5984e6c3f..560abb09d 100644 --- a/nms/v1_19_R2/src/main/java/com/volmit/iris/core/nms/v1_19_R2/NMSBinding.java +++ b/nms/v1_19_R2/src/main/java/com/volmit/iris/core/nms/v1_19_R2/NMSBinding.java @@ -23,12 +23,8 @@ import java.io.*; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.Iterator; -import java.util.List; +import java.util.*; import java.util.Map; -import java.util.Optional; -import java.util.Map; -import java.util.Vector; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Logger; @@ -43,8 +39,16 @@ import com.volmit.iris.util.io.IO; import com.volmit.iris.util.scheduling.J; import net.minecraft.nbt.*; import net.minecraft.nbt.Tag; +import net.minecraft.network.Connection; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket; import net.minecraft.resources.RegistryOps; +import net.minecraft.server.PlayerAdvancements; import net.minecraft.server.commands.data.BlockDataAccessor; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerLoginPacketListenerImpl; +import net.minecraft.server.players.PlayerList; +import net.minecraft.stats.ServerStatsCounter; +import net.minecraft.stats.Stats; import net.minecraft.tags.TagKey; import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.biome.MobSpawnSettings; @@ -85,10 +89,14 @@ import org.bukkit.craftbukkit.v1_19_R2.block.CraftBlockState; import org.bukkit.craftbukkit.v1_19_R2.block.CraftBlockStates; import org.bukkit.craftbukkit.v1_19_R2.block.data.CraftBlockData; import org.bukkit.craftbukkit.v1_19_R2.entity.CraftDolphin; +import org.bukkit.craftbukkit.v1_19_R2.entity.CraftPlayer; import org.bukkit.craftbukkit.v1_19_R2.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_19_R2.scoreboard.CraftScoreboardManager; import org.bukkit.entity.Dolphin; import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; @@ -829,6 +837,99 @@ public class NMSBinding implements INMSBinding { return keys; } + @Override + public void reconnect(Player player) { + var serverPlayer = ((CraftPlayer) player).getHandle(); + var listener = serverPlayer.connection; + + try { + var field = getField(listener.getClass(), Connection.class); + field.setAccessible(true); + var connection = (Connection) field.get(listener); + var server = serverPlayer.getServer(); + var playerList = server.getPlayerList(); + J.s(() -> { + try { + remove(serverPlayer); + } catch (Throwable e) { + Iris.error("Failed to remove player " + player.getName()); + e.printStackTrace(); + return; + } + var result = playerList.canPlayerLogin(new ServerLoginPacketListenerImpl(server, connection), serverPlayer.getGameProfile()); + if (result != null) { + playerList.placeNewPlayer(connection, result); + } + }); + } catch (Throwable e) { + Iris.error("Failed to reconnect player " + player.getName()); + e.printStackTrace(); + } + } + + private void remove(ServerPlayer player) throws NoSuchFieldException, IllegalAccessException { + ServerLevel level = player.getLevel(); + player.awardStat(Stats.LEAVE_GAME); + if (player.containerMenu != player.inventoryMenu) { + player.closeContainer(); + } + + PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(player.getBukkitEntity(), "§e" + player.getScoreboardName() + " left the game"); + Bukkit.getPluginManager().callEvent(playerQuitEvent); + player.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); + player.doTick(); + + level.getServer().getPlayerList().playerIo.save(player); + ServerStatsCounter stats = player.getStats(); + if (stats != null) stats.save(); + PlayerAdvancements advancements = player.getAdvancements(); + if (advancements != null) advancements.save(); + + if (player.isPassenger()) { + var vehicle = player.getRootVehicle(); + if (vehicle.hasExactlyOnePlayerPassenger()) { + Iris.debug("Removing player mount"); + player.stopRiding(); + vehicle.getPassengersAndSelf().forEach(passenger -> passenger.setRemoved(net.minecraft.world.entity.Entity.RemovalReason.UNLOADED_WITH_PLAYER)); + } + } + + player.unRide(); + level.removePlayerImmediately(player, net.minecraft.world.entity.Entity.RemovalReason.UNLOADED_WITH_PLAYER); + player.getAdvancements().stopListening(); + + var playersField = getField(PlayerList.class, buildType(List.class, ServerPlayer.class.getName())); + playersField.setAccessible(true); + var playersByNameField = getField(PlayerList.class, buildType(Map.class, String.class.getName(), ServerPlayer.class.getName())); + playersByNameField.setAccessible(true); + var playersByUUIDField = getField(PlayerList.class, buildType(Map.class, UUID.class.getName(), ServerPlayer.class.getName())); + playersByUUIDField.setAccessible(true); + + var players = (List)playersField.get(player.getServer().getPlayerList()); + var playersByName = (Map)playersByNameField.get(player.getServer().getPlayerList()); + var playersByUUID = (Map)playersByUUIDField.get(player.getServer().getPlayerList()); + + players.remove(player); + playersByName.remove(player.getScoreboardName().toLowerCase(Locale.ROOT)); + level.getServer().getCustomBossEvents().onPlayerDisconnect(player); + UUID uuid = player.getUUID(); + ServerPlayer currentPlayer = playersByUUID.get(uuid); + if (currentPlayer == player) { + playersByUUID.remove(uuid); + } + + ClientboundPlayerInfoRemovePacket packet = new ClientboundPlayerInfoRemovePacket(List.of(player.getUUID())); + for (ServerPlayer target : players) { + if (target.getBukkitEntity().canSee(player.getBukkitEntity())) { + target.connection.send(packet); + } else { + target.getBukkitEntity().onEntityRemove(player); + } + } + + ((CraftScoreboardManager) Bukkit.getScoreboardManager()).removePlayer(player.getBukkitEntity()); + } + private static Field getField(Class clazz, Class fieldType) throws NoSuchFieldException { try { for (Field f : clazz.getDeclaredFields()) { diff --git a/nms/v1_19_R3/src/main/java/com/volmit/iris/core/nms/v1_19_R3/NMSBinding.java b/nms/v1_19_R3/src/main/java/com/volmit/iris/core/nms/v1_19_R3/NMSBinding.java index 6d765f814..e59cbc12f 100644 --- a/nms/v1_19_R3/src/main/java/com/volmit/iris/core/nms/v1_19_R3/NMSBinding.java +++ b/nms/v1_19_R3/src/main/java/com/volmit/iris/core/nms/v1_19_R3/NMSBinding.java @@ -23,11 +23,7 @@ import java.io.*; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Vector; +import java.util.*; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Logger; @@ -40,10 +36,18 @@ import com.volmit.iris.engine.object.IrisBiomeReplacement; import com.volmit.iris.util.scheduling.J; import net.minecraft.nbt.*; import net.minecraft.nbt.Tag; +import net.minecraft.network.Connection; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket; import net.minecraft.resources.RegistryOps; +import net.minecraft.server.PlayerAdvancements; import net.minecraft.server.commands.data.BlockDataAccessor; import com.volmit.iris.core.nms.container.IPackRepository; import com.volmit.iris.util.io.IO; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerLoginPacketListenerImpl; +import net.minecraft.server.players.PlayerList; +import net.minecraft.stats.ServerStatsCounter; +import net.minecraft.stats.Stats; import net.minecraft.tags.TagKey; import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.biome.BiomeGenerationSettings; @@ -85,10 +89,14 @@ import org.bukkit.craftbukkit.v1_19_R3.block.CraftBlockState; import org.bukkit.craftbukkit.v1_19_R3.block.CraftBlockStates; import org.bukkit.craftbukkit.v1_19_R3.block.data.CraftBlockData; import org.bukkit.craftbukkit.v1_19_R3.entity.CraftDolphin; +import org.bukkit.craftbukkit.v1_19_R3.entity.CraftPlayer; import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_19_R3.scoreboard.CraftScoreboardManager; import org.bukkit.entity.Dolphin; import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; @@ -847,6 +855,99 @@ public class NMSBinding implements INMSBinding { return keys; } + @Override + public void reconnect(Player player) { + var serverPlayer = ((CraftPlayer) player).getHandle(); + var listener = serverPlayer.connection; + + try { + var field = getField(listener.getClass(), Connection.class); + field.setAccessible(true); + var connection = (Connection) field.get(listener); + var server = serverPlayer.getServer(); + var playerList = server.getPlayerList(); + J.s(() -> { + try { + remove(serverPlayer); + } catch (Throwable e) { + Iris.error("Failed to remove player " + player.getName()); + e.printStackTrace(); + return; + } + var result = playerList.canPlayerLogin(new ServerLoginPacketListenerImpl(server, connection), serverPlayer.getGameProfile()); + if (result != null) { + playerList.placeNewPlayer(connection, result); + } + }); + } catch (Throwable e) { + Iris.error("Failed to reconnect player " + player.getName()); + e.printStackTrace(); + } + } + + private void remove(ServerPlayer player) throws NoSuchFieldException, IllegalAccessException { + ServerLevel level = player.getLevel(); + player.awardStat(Stats.LEAVE_GAME); + if (player.containerMenu != player.inventoryMenu) { + player.closeContainer(); + } + + PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(player.getBukkitEntity(), "§e" + player.getScoreboardName() + " left the game"); + Bukkit.getPluginManager().callEvent(playerQuitEvent); + player.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); + player.doTick(); + + level.getServer().getPlayerList().playerIo.save(player); + ServerStatsCounter stats = player.getStats(); + if (stats != null) stats.save(); + PlayerAdvancements advancements = player.getAdvancements(); + if (advancements != null) advancements.save(); + + if (player.isPassenger()) { + var vehicle = player.getRootVehicle(); + if (vehicle.hasExactlyOnePlayerPassenger()) { + Iris.debug("Removing player mount"); + player.stopRiding(); + vehicle.getPassengersAndSelf().forEach(passenger -> passenger.setRemoved(net.minecraft.world.entity.Entity.RemovalReason.UNLOADED_WITH_PLAYER)); + } + } + + player.unRide(); + level.removePlayerImmediately(player, net.minecraft.world.entity.Entity.RemovalReason.UNLOADED_WITH_PLAYER); + player.getAdvancements().stopListening(); + + var playersField = getField(PlayerList.class, buildType(List.class, ServerPlayer.class.getName())); + playersField.setAccessible(true); + var playersByNameField = getField(PlayerList.class, buildType(Map.class, String.class.getName(), ServerPlayer.class.getName())); + playersByNameField.setAccessible(true); + var playersByUUIDField = getField(PlayerList.class, buildType(Map.class, UUID.class.getName(), ServerPlayer.class.getName())); + playersByUUIDField.setAccessible(true); + + var players = (List)playersField.get(player.getServer().getPlayerList()); + var playersByName = (Map)playersByNameField.get(player.getServer().getPlayerList()); + var playersByUUID = (Map)playersByUUIDField.get(player.getServer().getPlayerList()); + + players.remove(player); + playersByName.remove(player.getScoreboardName().toLowerCase(Locale.ROOT)); + level.getServer().getCustomBossEvents().onPlayerDisconnect(player); + UUID uuid = player.getUUID(); + ServerPlayer currentPlayer = playersByUUID.get(uuid); + if (currentPlayer == player) { + playersByUUID.remove(uuid); + } + + ClientboundPlayerInfoRemovePacket packet = new ClientboundPlayerInfoRemovePacket(List.of(player.getUUID())); + for (ServerPlayer target : players) { + if (target.getBukkitEntity().canSee(player.getBukkitEntity())) { + target.connection.send(packet); + } else { + target.getBukkitEntity().onEntityRemove(player); + } + } + + ((CraftScoreboardManager) Bukkit.getScoreboardManager()).removePlayer(player.getBukkitEntity()); + } + private static Field getField(Class clazz, Class fieldType) throws NoSuchFieldException { try { for (Field f : clazz.getDeclaredFields()) { diff --git a/nms/v1_20_R1/src/main/java/com/volmit/iris/core/nms/v1_20_R1/NMSBinding.java b/nms/v1_20_R1/src/main/java/com/volmit/iris/core/nms/v1_20_R1/NMSBinding.java index e6bff2cb3..bef72d1ee 100644 --- a/nms/v1_20_R1/src/main/java/com/volmit/iris/core/nms/v1_20_R1/NMSBinding.java +++ b/nms/v1_20_R1/src/main/java/com/volmit/iris/core/nms/v1_20_R1/NMSBinding.java @@ -65,12 +65,20 @@ import net.minecraft.core.RegistryAccess; import net.minecraft.core.registries.Registries; import net.minecraft.nbt.*; import net.minecraft.nbt.Tag; +import net.minecraft.network.Connection; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket; import net.minecraft.resources.RegistryOps; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; +import net.minecraft.server.PlayerAdvancements; import net.minecraft.server.commands.data.BlockDataAccessor; import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerLoginPacketListenerImpl; +import net.minecraft.server.players.PlayerList; +import net.minecraft.stats.ServerStatsCounter; +import net.minecraft.stats.Stats; import net.minecraft.tags.TagKey; import net.minecraft.world.entity.EntityDimensions; import net.minecraft.world.level.LevelReader; @@ -104,10 +112,14 @@ import org.bukkit.craftbukkit.v1_20_R1.block.CraftBlockState; import org.bukkit.craftbukkit.v1_20_R1.block.CraftBlockStates; import org.bukkit.craftbukkit.v1_20_R1.block.data.CraftBlockData; import org.bukkit.craftbukkit.v1_20_R1.entity.CraftDolphin; +import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer; import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_20_R1.scoreboard.CraftScoreboardManager; import org.bukkit.entity.Dolphin; import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; @@ -120,11 +132,7 @@ import java.io.*; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Vector; +import java.util.*; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Logger; @@ -839,6 +847,99 @@ public class NMSBinding implements INMSBinding { return keys; } + @Override + public void reconnect(Player player) { + var serverPlayer = ((CraftPlayer) player).getHandle(); + var listener = serverPlayer.connection; + + try { + var field = getField(listener.getClass(), Connection.class); + field.setAccessible(true); + var connection = (Connection) field.get(listener); + var server = serverPlayer.getServer(); + var playerList = server.getPlayerList(); + J.s(() -> { + try { + remove(serverPlayer); + } catch (Throwable e) { + Iris.error("Failed to remove player " + player.getName()); + e.printStackTrace(); + return; + } + var result = playerList.canPlayerLogin(new ServerLoginPacketListenerImpl(server, connection), serverPlayer.getGameProfile()); + if (result != null) { + playerList.placeNewPlayer(connection, result); + } + }); + } catch (Throwable e) { + Iris.error("Failed to reconnect player " + player.getName()); + e.printStackTrace(); + } + } + + private void remove(ServerPlayer player) throws NoSuchFieldException, IllegalAccessException { + ServerLevel level = player.serverLevel(); + player.awardStat(Stats.LEAVE_GAME); + if (player.containerMenu != player.inventoryMenu) { + player.closeContainer(); + } + + PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(player.getBukkitEntity(), "§e" + player.getScoreboardName() + " left the game"); + Bukkit.getPluginManager().callEvent(playerQuitEvent); + player.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); + player.doTick(); + + level.getServer().getPlayerList().playerIo.save(player); + ServerStatsCounter stats = player.getStats(); + if (stats != null) stats.save(); + PlayerAdvancements advancements = player.getAdvancements(); + if (advancements != null) advancements.save(); + + if (player.isPassenger()) { + var vehicle = player.getRootVehicle(); + if (vehicle.hasExactlyOnePlayerPassenger()) { + Iris.debug("Removing player mount"); + player.stopRiding(); + vehicle.getPassengersAndSelf().forEach(passenger -> passenger.setRemoved(net.minecraft.world.entity.Entity.RemovalReason.UNLOADED_WITH_PLAYER)); + } + } + + player.unRide(); + level.removePlayerImmediately(player, net.minecraft.world.entity.Entity.RemovalReason.UNLOADED_WITH_PLAYER); + player.getAdvancements().stopListening(); + + var playersField = getField(PlayerList.class, buildType(List.class, ServerPlayer.class.getName())); + playersField.setAccessible(true); + var playersByNameField = getField(PlayerList.class, buildType(Map.class, String.class.getName(), ServerPlayer.class.getName())); + playersByNameField.setAccessible(true); + var playersByUUIDField = getField(PlayerList.class, buildType(Map.class, UUID.class.getName(), ServerPlayer.class.getName())); + playersByUUIDField.setAccessible(true); + + var players = (List)playersField.get(player.getServer().getPlayerList()); + var playersByName = (Map)playersByNameField.get(player.getServer().getPlayerList()); + var playersByUUID = (Map)playersByUUIDField.get(player.getServer().getPlayerList()); + + players.remove(player); + playersByName.remove(player.getScoreboardName().toLowerCase(Locale.ROOT)); + level.getServer().getCustomBossEvents().onPlayerDisconnect(player); + UUID uuid = player.getUUID(); + ServerPlayer currentPlayer = playersByUUID.get(uuid); + if (currentPlayer == player) { + playersByUUID.remove(uuid); + } + + ClientboundPlayerInfoRemovePacket packet = new ClientboundPlayerInfoRemovePacket(List.of(player.getUUID())); + for (ServerPlayer target : players) { + if (target.getBukkitEntity().canSee(player.getBukkitEntity())) { + target.connection.send(packet); + } else { + target.getBukkitEntity().onEntityRemove(player); + } + } + + ((CraftScoreboardManager) Bukkit.getScoreboardManager()).removePlayer(player.getBukkitEntity()); + } + private static Field getField(Class clazz, Class fieldType) throws NoSuchFieldException { try { for (Field f : clazz.getDeclaredFields()) { diff --git a/nms/v1_20_R2/src/main/java/com/volmit/iris/core/nms/v1_20_R2/NMSBinding.java b/nms/v1_20_R2/src/main/java/com/volmit/iris/core/nms/v1_20_R2/NMSBinding.java index 5d23a7263..2d316d760 100644 --- a/nms/v1_20_R2/src/main/java/com/volmit/iris/core/nms/v1_20_R2/NMSBinding.java +++ b/nms/v1_20_R2/src/main/java/com/volmit/iris/core/nms/v1_20_R2/NMSBinding.java @@ -23,11 +23,7 @@ import java.io.*; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Vector; +import java.util.*; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Logger; @@ -40,8 +36,17 @@ import com.volmit.iris.engine.object.IrisBiomeReplacement; import com.volmit.iris.util.scheduling.J; import net.minecraft.nbt.*; import net.minecraft.nbt.Tag; +import net.minecraft.network.Connection; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket; import net.minecraft.resources.RegistryOps; +import net.minecraft.server.PlayerAdvancements; import net.minecraft.server.commands.data.BlockDataAccessor; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.CommonListenerCookie; +import net.minecraft.server.network.ServerLoginPacketListenerImpl; +import net.minecraft.server.players.PlayerList; +import net.minecraft.stats.ServerStatsCounter; +import net.minecraft.stats.Stats; import net.minecraft.tags.TagKey; import com.volmit.iris.core.nms.container.IPackRepository; import com.volmit.iris.util.io.IO; @@ -85,12 +90,16 @@ import org.bukkit.craftbukkit.v1_20_R2.block.CraftBlockState; import org.bukkit.craftbukkit.v1_20_R2.block.CraftBlockStates; import org.bukkit.craftbukkit.v1_20_R2.block.data.CraftBlockData; import org.bukkit.craftbukkit.v1_20_R2.entity.CraftDolphin; +import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer; import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_20_R2.packs.CraftDataPackManager; +import org.bukkit.craftbukkit.v1_20_R2.scoreboard.CraftScoreboardManager; import org.bukkit.craftbukkit.v1_20_R2.util.CraftNamespacedKey; import org.bukkit.entity.Dolphin; import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; @@ -841,6 +850,101 @@ public class NMSBinding implements INMSBinding { return keys; } + @Override + public void reconnect(Player player) { + var serverPlayer = ((CraftPlayer) player).getHandle(); + var listener = serverPlayer.connection; + var cookie = new CommonListenerCookie(serverPlayer.getGameProfile(), listener.latency(), serverPlayer.clientInformation()); + + try { + var field = getField(listener.getClass(), Connection.class); + field.setAccessible(true); + var connection = (Connection) field.get(listener); + var server = serverPlayer.getServer(); + var playerList = server.getPlayerList(); + J.s(() -> { + try { + remove(serverPlayer); + } catch (Throwable e) { + Iris.error("Failed to remove player " + player.getName()); + e.printStackTrace(); + return; + } + var result = playerList.canPlayerLogin(new ServerLoginPacketListenerImpl(server, connection), cookie.gameProfile()); + if (result != null) { + result.updateOptions(cookie.clientInformation()); + playerList.placeNewPlayer(connection, result, cookie); + } + }); + } catch (Throwable e) { + Iris.error("Failed to reconnect player " + player.getName()); + e.printStackTrace(); + } + } + + private void remove(ServerPlayer player) throws NoSuchFieldException, IllegalAccessException { + ServerLevel level = player.serverLevel(); + player.awardStat(Stats.LEAVE_GAME); + if (player.containerMenu != player.inventoryMenu) { + player.closeContainer(); + } + + PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(player.getBukkitEntity(), "§e" + player.getScoreboardName() + " left the game"); + Bukkit.getPluginManager().callEvent(playerQuitEvent); + player.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); + player.doTick(); + + level.getServer().getPlayerList().playerIo.save(player); + ServerStatsCounter stats = player.getStats(); + if (stats != null) stats.save(); + PlayerAdvancements advancements = player.getAdvancements(); + if (advancements != null) advancements.save(); + + if (player.isPassenger()) { + var vehicle = player.getRootVehicle(); + if (vehicle.hasExactlyOnePlayerPassenger()) { + Iris.debug("Removing player mount"); + player.stopRiding(); + vehicle.getPassengersAndSelf().forEach(passenger -> passenger.setRemoved(net.minecraft.world.entity.Entity.RemovalReason.UNLOADED_WITH_PLAYER)); + } + } + + player.unRide(); + level.removePlayerImmediately(player, net.minecraft.world.entity.Entity.RemovalReason.UNLOADED_WITH_PLAYER); + player.getAdvancements().stopListening(); + + var playersField = getField(PlayerList.class, buildType(List.class, ServerPlayer.class.getName())); + playersField.setAccessible(true); + var playersByNameField = getField(PlayerList.class, buildType(Map.class, String.class.getName(), ServerPlayer.class.getName())); + playersByNameField.setAccessible(true); + var playersByUUIDField = getField(PlayerList.class, buildType(Map.class, UUID.class.getName(), ServerPlayer.class.getName())); + playersByUUIDField.setAccessible(true); + + var players = (List)playersField.get(player.getServer().getPlayerList()); + var playersByName = (Map)playersByNameField.get(player.getServer().getPlayerList()); + var playersByUUID = (Map)playersByUUIDField.get(player.getServer().getPlayerList()); + + players.remove(player); + playersByName.remove(player.getScoreboardName().toLowerCase(Locale.ROOT)); + level.getServer().getCustomBossEvents().onPlayerDisconnect(player); + UUID uuid = player.getUUID(); + ServerPlayer currentPlayer = playersByUUID.get(uuid); + if (currentPlayer == player) { + playersByUUID.remove(uuid); + } + + ClientboundPlayerInfoRemovePacket packet = new ClientboundPlayerInfoRemovePacket(List.of(player.getUUID())); + for (ServerPlayer target : players) { + if (target.getBukkitEntity().canSee(player.getBukkitEntity())) { + target.connection.send(packet); + } else { + target.getBukkitEntity().onEntityRemove(player); + } + } + + ((CraftScoreboardManager) Bukkit.getScoreboardManager()).removePlayer(player.getBukkitEntity()); + } + private static Field getField(Class clazz, Class fieldType) throws NoSuchFieldException { try { for (Field f : clazz.getDeclaredFields()) { diff --git a/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/NMSBinding.java b/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/NMSBinding.java index ec41a20f7..1448d4786 100644 --- a/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/NMSBinding.java +++ b/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/NMSBinding.java @@ -33,7 +33,6 @@ import java.util.stream.Collectors; import com.google.common.base.Preconditions; import com.google.gson.JsonElement; import com.google.gson.JsonNull; -import com.google.gson.JsonObject; import com.mojang.datafixers.util.Pair; import com.mojang.serialization.Codec; import com.mojang.serialization.JsonOps; @@ -50,25 +49,25 @@ import com.volmit.iris.util.io.IO; import it.unimi.dsi.fastutil.objects.Reference2IntMap; import net.bytebuddy.ByteBuddy; import net.bytebuddy.asm.Advice; -import net.bytebuddy.description.method.MethodDescription; -import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.dynamic.loading.ClassReloadingStrategy; -import net.bytebuddy.implementation.bytecode.StackManipulation; -import net.bytebuddy.implementation.bytecode.assign.Assigner; -import net.bytebuddy.implementation.bytecode.member.MethodInvocation; import net.bytebuddy.matcher.ElementMatchers; import net.minecraft.core.IdMapper; import net.minecraft.core.MappedRegistry; -import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.Connection; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket; import net.minecraft.resources.RegistryOps; import net.minecraft.server.MinecraftServer; +import net.minecraft.server.PlayerAdvancements; +import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.server.network.CommonListenerCookie; +import net.minecraft.server.network.ServerLoginPacketListenerImpl; +import net.minecraft.server.players.PlayerList; +import net.minecraft.stats.ServerStatsCounter; +import net.minecraft.stats.Stats; import net.minecraft.util.GsonHelper; import net.minecraft.world.RandomSequences; import net.minecraft.world.entity.MobCategory; -import net.minecraft.world.entity.MobSpawnType; -import net.minecraft.world.entity.MobType; -import net.minecraft.world.entity.ai.behavior.TryLaySpawnOnWaterNearLand; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelHeightAccessor; @@ -80,7 +79,6 @@ import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.PrimaryLevelData; -import com.mojang.datafixers.util.Pair; import com.volmit.iris.core.nms.container.BiomeColor; import com.volmit.iris.util.scheduling.J; import net.minecraft.nbt.*; @@ -98,14 +96,14 @@ import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; import org.bukkit.craftbukkit.v1_20_R3.block.CraftBlockState; import org.bukkit.craftbukkit.v1_20_R3.block.CraftBlockStates; import org.bukkit.craftbukkit.v1_20_R3.block.data.CraftBlockData; -import org.bukkit.craftbukkit.v1_20_R3.entity.CraftDolphin; +import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_20_R3.scoreboard.CraftScoreboardManager; import org.bukkit.craftbukkit.v1_20_R3.util.CraftNamespacedKey; -import org.bukkit.entity.Dolphin; -import org.bukkit.entity.Entity; -import org.bukkit.entity.EntityCategory; -import org.bukkit.entity.Mob; +import org.bukkit.entity.*; import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.entity.EntityRemoveEvent; +import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; @@ -124,7 +122,6 @@ import com.volmit.iris.util.json.JSONObject; import com.volmit.iris.util.mantle.Mantle; import com.volmit.iris.util.math.Vector3d; import com.volmit.iris.util.matter.MatterBiomeInject; -import com.volmit.iris.util.nbt.io.NBTUtil; import com.volmit.iris.util.nbt.mca.NBTWorld; import com.volmit.iris.util.nbt.mca.palette.*; import com.volmit.iris.util.nbt.tag.CompoundTag; @@ -139,14 +136,12 @@ import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.EntityType; -import net.minecraft.world.level.biome.BiomeSource; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.LevelChunk; -import sun.misc.Unsafe; import static net.bytebuddy.matcher.ElementMatchers.takesArguments; @@ -893,6 +888,101 @@ public class NMSBinding implements INMSBinding { return keys; } + @Override + public void reconnect(Player player) { + var serverPlayer = ((CraftPlayer) player).getHandle(); + var listener = serverPlayer.connection; + var cookie = new CommonListenerCookie(serverPlayer.getGameProfile(), listener.latency(), serverPlayer.clientInformation()); + + try { + var field = getField(listener.getClass(), Connection.class); + field.setAccessible(true); + var connection = (Connection) field.get(listener); + var server = serverPlayer.getServer(); + var playerList = server.getPlayerList(); + J.s(() -> { + try { + remove(serverPlayer); + } catch (Throwable e) { + Iris.error("Failed to remove player " + player.getName()); + e.printStackTrace(); + return; + } + var result = playerList.canPlayerLogin(new ServerLoginPacketListenerImpl(server, connection), cookie.gameProfile()); + if (result != null) { + result.updateOptions(cookie.clientInformation()); + playerList.placeNewPlayer(connection, result, cookie); + } + }); + } catch (Throwable e) { + Iris.error("Failed to reconnect player " + player.getName()); + e.printStackTrace(); + } + } + + private void remove(ServerPlayer player) throws NoSuchFieldException, IllegalAccessException { + ServerLevel level = player.serverLevel(); + player.awardStat(Stats.LEAVE_GAME); + if (player.containerMenu != player.inventoryMenu) { + player.closeContainer(); + } + + PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(player.getBukkitEntity(), "§e" + player.getScoreboardName() + " left the game"); + Bukkit.getPluginManager().callEvent(playerQuitEvent); + player.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); + player.doTick(); + + level.getServer().getPlayerList().playerIo.save(player); + ServerStatsCounter stats = player.getStats(); + if (stats != null) stats.save(); + PlayerAdvancements advancements = player.getAdvancements(); + if (advancements != null) advancements.save(); + + if (player.isPassenger()) { + var vehicle = player.getRootVehicle(); + if (vehicle.hasExactlyOnePlayerPassenger()) { + Iris.debug("Removing player mount"); + player.stopRiding(); + vehicle.getPassengersAndSelf().forEach(passenger -> passenger.setRemoved(net.minecraft.world.entity.Entity.RemovalReason.UNLOADED_WITH_PLAYER)); + } + } + + player.unRide(); + level.removePlayerImmediately(player, net.minecraft.world.entity.Entity.RemovalReason.UNLOADED_WITH_PLAYER); + player.getAdvancements().stopListening(); + + var playersField = getField(PlayerList.class, buildType(List.class, ServerPlayer.class.getName())); + playersField.setAccessible(true); + var playersByNameField = getField(PlayerList.class, buildType(Map.class, String.class.getName(), ServerPlayer.class.getName())); + playersByNameField.setAccessible(true); + var playersByUUIDField = getField(PlayerList.class, buildType(Map.class, UUID.class.getName(), ServerPlayer.class.getName())); + playersByUUIDField.setAccessible(true); + + var players = (List)playersField.get(player.getServer().getPlayerList()); + var playersByName = (Map)playersByNameField.get(player.getServer().getPlayerList()); + var playersByUUID = (Map)playersByUUIDField.get(player.getServer().getPlayerList()); + + players.remove(player); + playersByName.remove(player.getScoreboardName().toLowerCase(Locale.ROOT)); + level.getServer().getCustomBossEvents().onPlayerDisconnect(player); + UUID uuid = player.getUUID(); + ServerPlayer currentPlayer = playersByUUID.get(uuid); + if (currentPlayer == player) { + playersByUUID.remove(uuid); + } + + ClientboundPlayerInfoRemovePacket packet = new ClientboundPlayerInfoRemovePacket(List.of(player.getUUID())); + for (ServerPlayer target : players) { + if (target.getBukkitEntity().canSee(player.getBukkitEntity())) { + target.connection.send(packet); + } else { + target.getBukkitEntity().onEntityRemove(player); + } + } + + ((CraftScoreboardManager) Bukkit.getScoreboardManager()).removePlayer(player.getBukkitEntity()); + } + private static Field getField(Class clazz, Class fieldType) throws NoSuchFieldException { try { for (Field f : clazz.getDeclaredFields()) { diff --git a/nms/v1_20_R4/src/main/java/com/volmit/iris/core/nms/v1_20_R4/NMSBinding.java b/nms/v1_20_R4/src/main/java/com/volmit/iris/core/nms/v1_20_R4/NMSBinding.java index 188df2c68..b7fe4bfa1 100644 --- a/nms/v1_20_R4/src/main/java/com/volmit/iris/core/nms/v1_20_R4/NMSBinding.java +++ b/nms/v1_20_R4/src/main/java/com/volmit/iris/core/nms/v1_20_R4/NMSBinding.java @@ -23,11 +23,7 @@ import java.io.*; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Vector; +import java.util.*; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Logger; @@ -61,9 +57,18 @@ import com.volmit.iris.util.scheduling.J; import net.minecraft.core.*; import net.minecraft.core.Registry; import net.minecraft.core.component.DataComponents; +import net.minecraft.network.Connection; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket; import net.minecraft.resources.RegistryOps; import net.minecraft.server.MinecraftServer; +import net.minecraft.server.PlayerAdvancements; +import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.server.network.CommonListenerCookie; +import net.minecraft.server.network.ServerLoginPacketListenerImpl; +import net.minecraft.server.players.PlayerList; +import net.minecraft.stats.ServerStatsCounter; +import net.minecraft.stats.Stats; import net.minecraft.util.GsonHelper; import net.minecraft.world.RandomSequences; import net.minecraft.nbt.*; @@ -103,12 +108,16 @@ import org.bukkit.craftbukkit.v1_20_R4.block.CraftBlockStates; import org.bukkit.craftbukkit.v1_20_R4.block.CraftBlockType; import org.bukkit.craftbukkit.v1_20_R4.block.data.CraftBlockData; import org.bukkit.craftbukkit.v1_20_R4.entity.CraftDolphin; +import org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer; import org.bukkit.craftbukkit.v1_20_R4.generator.CustomChunkGenerator; import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_20_R4.scoreboard.CraftScoreboardManager; import org.bukkit.craftbukkit.v1_20_R4.util.CraftNamespacedKey; import org.bukkit.entity.Dolphin; import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; @@ -935,6 +944,101 @@ public class NMSBinding implements INMSBinding { return keys; } + @Override + public void reconnect(Player player) { + var serverPlayer = ((CraftPlayer) player).getHandle(); + var listener = serverPlayer.connection; + var cookie = new CommonListenerCookie(serverPlayer.getGameProfile(), listener.latency(), serverPlayer.clientInformation(), false); + + try { + var field = getField(listener.getClass(), Connection.class); + field.setAccessible(true); + var connection = (Connection) field.get(listener); + var server = serverPlayer.getServer(); + var playerList = server.getPlayerList(); + J.s(() -> { + try { + remove(serverPlayer); + } catch (Throwable e) { + Iris.error("Failed to remove player " + player.getName()); + e.printStackTrace(); + return; + } + var result = playerList.canPlayerLogin(new ServerLoginPacketListenerImpl(server, connection, false), cookie.gameProfile()); + if (result != null) { + result.updateOptions(cookie.clientInformation()); + playerList.placeNewPlayer(connection, result, cookie); + } + }); + } catch (Throwable e) { + Iris.error("Failed to reconnect player " + player.getName()); + e.printStackTrace(); + } + } + + private void remove(ServerPlayer player) throws NoSuchFieldException, IllegalAccessException { + ServerLevel level = player.serverLevel(); + player.awardStat(Stats.LEAVE_GAME); + if (player.containerMenu != player.inventoryMenu) { + player.closeContainer(); + } + + PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(player.getBukkitEntity(), "§e" + player.getScoreboardName() + " left the game"); + Bukkit.getPluginManager().callEvent(playerQuitEvent); + player.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); + player.doTick(); + + level.getServer().getPlayerList().playerIo.save(player); + ServerStatsCounter stats = player.getStats(); + if (stats != null) stats.save(); + PlayerAdvancements advancements = player.getAdvancements(); + if (advancements != null) advancements.save(); + + if (player.isPassenger()) { + var vehicle = player.getRootVehicle(); + if (vehicle.hasExactlyOnePlayerPassenger()) { + Iris.debug("Removing player mount"); + player.stopRiding(); + vehicle.getPassengersAndSelf().forEach(passenger -> passenger.setRemoved(net.minecraft.world.entity.Entity.RemovalReason.UNLOADED_WITH_PLAYER)); + } + } + + player.unRide(); + level.removePlayerImmediately(player, net.minecraft.world.entity.Entity.RemovalReason.UNLOADED_WITH_PLAYER); + player.getAdvancements().stopListening(); + + var playersField = getField(PlayerList.class, buildType(List.class, ServerPlayer.class.getName())); + playersField.setAccessible(true); + var playersByNameField = getField(PlayerList.class, buildType(Map.class, String.class.getName(), ServerPlayer.class.getName())); + playersByNameField.setAccessible(true); + var playersByUUIDField = getField(PlayerList.class, buildType(Map.class, UUID.class.getName(), ServerPlayer.class.getName())); + playersByUUIDField.setAccessible(true); + + var players = (List)playersField.get(player.getServer().getPlayerList()); + var playersByName = (Map)playersByNameField.get(player.getServer().getPlayerList()); + var playersByUUID = (Map)playersByUUIDField.get(player.getServer().getPlayerList()); + + players.remove(player); + playersByName.remove(player.getScoreboardName().toLowerCase(Locale.ROOT)); + level.getServer().getCustomBossEvents().onPlayerDisconnect(player); + UUID uuid = player.getUUID(); + ServerPlayer currentPlayer = playersByUUID.get(uuid); + if (currentPlayer == player) { + playersByUUID.remove(uuid); + } + + ClientboundPlayerInfoRemovePacket packet = new ClientboundPlayerInfoRemovePacket(List.of(player.getUUID())); + for (ServerPlayer target : players) { + if (target.getBukkitEntity().canSee(player.getBukkitEntity())) { + target.connection.send(packet); + } else { + target.getBukkitEntity().onEntityRemove(player); + } + } + + ((CraftScoreboardManager) Bukkit.getScoreboardManager()).removePlayer(player.getBukkitEntity()); + } + @Override public IPackRepository getPackRepository() { return packRepository; diff --git a/nms/v1_21_R1/src/main/java/com/volmit/iris/core/nms/v1_21_R1/NMSBinding.java b/nms/v1_21_R1/src/main/java/com/volmit/iris/core/nms/v1_21_R1/NMSBinding.java index 3aa82c129..59f45d648 100644 --- a/nms/v1_21_R1/src/main/java/com/volmit/iris/core/nms/v1_21_R1/NMSBinding.java +++ b/nms/v1_21_R1/src/main/java/com/volmit/iris/core/nms/v1_21_R1/NMSBinding.java @@ -56,10 +56,19 @@ import com.volmit.iris.util.scheduling.J; import net.minecraft.core.component.DataComponents; import net.minecraft.nbt.*; import net.minecraft.nbt.Tag; +import net.minecraft.network.Connection; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket; import net.minecraft.resources.RegistryOps; +import net.minecraft.server.PlayerAdvancements; import net.minecraft.server.commands.data.BlockDataAccessor; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.CommonListenerCookie; +import net.minecraft.server.network.ServerLoginPacketListenerImpl; +import net.minecraft.server.players.PlayerList; +import net.minecraft.stats.ServerStatsCounter; +import net.minecraft.stats.Stats; import net.minecraft.tags.TagKey; import net.minecraft.server.level.progress.ChunkProgressListener; import net.minecraft.util.GsonHelper; @@ -86,11 +95,15 @@ import org.bukkit.craftbukkit.v1_21_R1.block.CraftBlockState; import org.bukkit.craftbukkit.v1_21_R1.block.CraftBlockStates; import org.bukkit.craftbukkit.v1_21_R1.block.data.CraftBlockData; import org.bukkit.craftbukkit.v1_21_R1.entity.CraftDolphin; +import org.bukkit.craftbukkit.v1_21_R1.entity.CraftPlayer; import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_21_R1.scoreboard.CraftScoreboardManager; import org.bukkit.craftbukkit.v1_21_R1.util.CraftNamespacedKey; import org.bukkit.entity.Dolphin; import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; @@ -930,6 +943,101 @@ public class NMSBinding implements INMSBinding { return keys; } + @Override + public void reconnect(Player player) { + var serverPlayer = ((CraftPlayer) player).getHandle(); + var listener = serverPlayer.connection; + var cookie = new CommonListenerCookie(serverPlayer.getGameProfile(), listener.latency(), serverPlayer.clientInformation(), false); + + try { + var field = getField(listener.getClass(), Connection.class); + field.setAccessible(true); + var connection = (Connection) field.get(listener); + var server = serverPlayer.getServer(); + var playerList = server.getPlayerList(); + J.s(() -> { + try { + remove(serverPlayer); + } catch (Throwable e) { + Iris.error("Failed to remove player " + player.getName()); + e.printStackTrace(); + return; + } + var result = playerList.canPlayerLogin(new ServerLoginPacketListenerImpl(server, connection, false), cookie.gameProfile()); + if (result != null) { + result.updateOptions(cookie.clientInformation()); + playerList.placeNewPlayer(connection, result, cookie); + } + }); + } catch (Throwable e) { + Iris.error("Failed to reconnect player " + player.getName()); + e.printStackTrace(); + } + } + + private void remove(ServerPlayer player) throws NoSuchFieldException, IllegalAccessException { + ServerLevel level = player.serverLevel(); + player.awardStat(Stats.LEAVE_GAME); + if (player.containerMenu != player.inventoryMenu) { + player.closeContainer(); + } + + PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(player.getBukkitEntity(), "§e" + player.getScoreboardName() + " left the game"); + Bukkit.getPluginManager().callEvent(playerQuitEvent); + player.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); + player.doTick(); + + level.getServer().getPlayerList().playerIo.save(player); + ServerStatsCounter stats = player.getStats(); + if (stats != null) stats.save(); + PlayerAdvancements advancements = player.getAdvancements(); + if (advancements != null) advancements.save(); + + if (player.isPassenger()) { + var vehicle = player.getRootVehicle(); + if (vehicle.hasExactlyOnePlayerPassenger()) { + Iris.debug("Removing player mount"); + player.stopRiding(); + vehicle.getPassengersAndSelf().forEach(passenger -> passenger.setRemoved(net.minecraft.world.entity.Entity.RemovalReason.UNLOADED_WITH_PLAYER)); + } + } + + player.unRide(); + level.removePlayerImmediately(player, net.minecraft.world.entity.Entity.RemovalReason.UNLOADED_WITH_PLAYER); + player.getAdvancements().stopListening(); + + var playersField = getField(PlayerList.class, buildType(List.class, ServerPlayer.class.getName())); + playersField.setAccessible(true); + var playersByNameField = getField(PlayerList.class, buildType(Map.class, String.class.getName(), ServerPlayer.class.getName())); + playersByNameField.setAccessible(true); + var playersByUUIDField = getField(PlayerList.class, buildType(Map.class, UUID.class.getName(), ServerPlayer.class.getName())); + playersByUUIDField.setAccessible(true); + + var players = (List)playersField.get(player.getServer().getPlayerList()); + var playersByName = (Map)playersByNameField.get(player.getServer().getPlayerList()); + var playersByUUID = (Map)playersByUUIDField.get(player.getServer().getPlayerList()); + + players.remove(player); + playersByName.remove(player.getScoreboardName().toLowerCase(Locale.ROOT)); + level.getServer().getCustomBossEvents().onPlayerDisconnect(player); + UUID uuid = player.getUUID(); + ServerPlayer currentPlayer = playersByUUID.get(uuid); + if (currentPlayer == player) { + playersByUUID.remove(uuid); + } + + ClientboundPlayerInfoRemovePacket packet = new ClientboundPlayerInfoRemovePacket(List.of(player.getUUID())); + for (ServerPlayer target : players) { + if (target.getBukkitEntity().canSee(player.getBukkitEntity())) { + target.connection.send(packet); + } else { + target.getBukkitEntity().onEntityRemove(player); + } + } + + ((CraftScoreboardManager) Bukkit.getScoreboardManager()).removePlayer(player.getBukkitEntity()); + } + @Override public IPackRepository getPackRepository() { return packRepository;