reconnect players on dimension type registry change

This commit is contained in:
Julian Krings 2024-09-06 14:58:09 +02:00
parent 613575c0c5
commit c98ed48ee2
12 changed files with 877 additions and 56 deletions

View File

@ -832,6 +832,7 @@ public class Iris extends VolmitPlugin implements Listener {
if (!INMS.get().registerDimension(worldName, dim)) { if (!INMS.get().registerDimension(worldName, dim)) {
throw new IllegalStateException("Unable to register dimension " + dim.getName()); throw new IllegalStateException("Unable to register dimension " + dim.getName());
} }
INMS.get().reconnectAll();
return new BukkitChunkGenerator(w, false, ff, dim.getLoadKey()); return new BukkitChunkGenerator(w, false, ff, dim.getLoadKey());
} }

View File

@ -36,6 +36,7 @@ import org.bukkit.block.Biome;
import org.bukkit.entity.Dolphin; import org.bukkit.entity.Dolphin;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.generator.ChunkGenerator; import org.bukkit.generator.ChunkGenerator;
import org.bukkit.generator.structure.Structure; import org.bukkit.generator.structure.Structure;
@ -43,6 +44,8 @@ import org.bukkit.inventory.ItemStack;
import java.io.File; import java.io.File;
import java.awt.Color; import java.awt.Color;
import java.util.ArrayList;
import java.util.Collection;
public interface INMSBinding { public interface INMSBinding {
boolean hasTile(Material material); boolean hasTile(Material material);
@ -147,4 +150,11 @@ public interface INMSBinding {
IPackRepository getPackRepository(); IPackRepository getPackRepository();
KList<String> getStructureKeys(); KList<String> getStructureKeys();
default void reconnectAll() {
new ArrayList<>(Bukkit.getOnlinePlayers())
.forEach(this::reconnect);
}
void reconnect(Player player);
} }

View File

@ -45,6 +45,7 @@ import org.bukkit.block.Biome;
import org.bukkit.entity.Dolphin; import org.bukkit.entity.Dolphin;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.generator.ChunkGenerator; import org.bukkit.generator.ChunkGenerator;
import org.bukkit.generator.structure.Structure; import org.bukkit.generator.structure.Structure;
@ -145,6 +146,11 @@ public class NMSBinding1X implements INMSBinding {
return new KList<>(list); return new KList<>(list);
} }
@Override
public void reconnect(Player player) {
}
@Override @Override
public CompoundTag serializeEntity(Entity location) { public CompoundTag serializeEntity(Entity location) {
return null; return null;

View File

@ -83,6 +83,7 @@ public class IrisWorldCreator {
if (!INMS.get().registerDimension(name, dim)) { if (!INMS.get().registerDimension(name, dim)) {
throw new IllegalStateException("Unable to register dimension " + dim.getName()); throw new IllegalStateException("Unable to register dimension " + dim.getName());
} }
INMS.get().reconnectAll();
return new WorldCreator(name) return new WorldCreator(name)
.environment(findEnvironment()) .environment(findEnvironment())

View File

@ -23,11 +23,7 @@ import java.io.*;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.Iterator; import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Vector;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -40,17 +36,23 @@ import com.volmit.iris.engine.object.IrisBiomeReplacement;
import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.J;
import net.minecraft.nbt.*; import net.minecraft.nbt.*;
import net.minecraft.nbt.Tag; 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.resources.RegistryOps;
import net.minecraft.server.PlayerAdvancements;
import net.minecraft.server.commands.data.BlockDataAccessor; import net.minecraft.server.commands.data.BlockDataAccessor;
import com.volmit.iris.core.nms.container.IPackRepository; import com.volmit.iris.core.nms.container.IPackRepository;
import com.volmit.iris.util.io.IO; 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.tags.TagKey;
import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.EntityBlock; import net.minecraft.world.level.block.EntityBlock;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.gson.JsonElement; 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.Codec;
import com.mojang.serialization.JsonOps; import com.mojang.serialization.JsonOps;
import com.mojang.serialization.Lifecycle; 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.CraftBlockState;
import org.bukkit.craftbukkit.v1_19_R1.block.CraftBlockStates; 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.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.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.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.ChunkGenerator; import org.bukkit.generator.ChunkGenerator;
import org.bukkit.inventory.ItemStack; 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.mantle.Mantle;
import com.volmit.iris.util.math.Vector3d; import com.volmit.iris.util.math.Vector3d;
import com.volmit.iris.util.matter.MatterBiomeInject; 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.NBTWorld;
import com.volmit.iris.util.nbt.mca.palette.*; import com.volmit.iris.util.nbt.mca.palette.*;
import com.volmit.iris.util.nbt.tag.CompoundTag; 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.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.EntityType; 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.Block;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunk;
import sun.misc.Unsafe;
public class NMSBinding implements INMSBinding { public class NMSBinding implements INMSBinding {
private final KMap<Biome, Object> baseBiomeCache = new KMap<>(); private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
@ -891,6 +892,99 @@ public class NMSBinding implements INMSBinding {
return keys; 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<ServerPlayer>)playersField.get(player.getServer().getPlayerList());
var playersByName = (Map<String, ServerPlayer>)playersByNameField.get(player.getServer().getPlayerList());
var playersByUUID = (Map<UUID, ServerPlayer>)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 { private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
try { try {
for (Field f : clazz.getDeclaredFields()) { for (Field f : clazz.getDeclaredFields()) {

View File

@ -23,12 +23,8 @@ import java.io.*;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.Iterator; import java.util.*;
import java.util.List;
import java.util.Map; 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.Executor;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -43,8 +39,16 @@ import com.volmit.iris.util.io.IO;
import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.J;
import net.minecraft.nbt.*; import net.minecraft.nbt.*;
import net.minecraft.nbt.Tag; 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.RegistryOps;
import net.minecraft.server.PlayerAdvancements;
import net.minecraft.server.commands.data.BlockDataAccessor; 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.tags.TagKey;
import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.biome.MobSpawnSettings; 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.CraftBlockStates;
import org.bukkit.craftbukkit.v1_19_R2.block.data.CraftBlockData; 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.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.inventory.CraftItemStack;
import org.bukkit.craftbukkit.v1_19_R2.scoreboard.CraftScoreboardManager;
import org.bukkit.entity.Dolphin; import org.bukkit.entity.Dolphin;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.ChunkGenerator; import org.bukkit.generator.ChunkGenerator;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
@ -829,6 +837,99 @@ public class NMSBinding implements INMSBinding {
return keys; 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<ServerPlayer>)playersField.get(player.getServer().getPlayerList());
var playersByName = (Map<String, ServerPlayer>)playersByNameField.get(player.getServer().getPlayerList());
var playersByUUID = (Map<UUID, ServerPlayer>)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 { private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
try { try {
for (Field f : clazz.getDeclaredFields()) { for (Field f : clazz.getDeclaredFields()) {

View File

@ -23,11 +23,7 @@ import java.io.*;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.Iterator; import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Vector;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -40,10 +36,18 @@ import com.volmit.iris.engine.object.IrisBiomeReplacement;
import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.J;
import net.minecraft.nbt.*; import net.minecraft.nbt.*;
import net.minecraft.nbt.Tag; 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.RegistryOps;
import net.minecraft.server.PlayerAdvancements;
import net.minecraft.server.commands.data.BlockDataAccessor; import net.minecraft.server.commands.data.BlockDataAccessor;
import com.volmit.iris.core.nms.container.IPackRepository; import com.volmit.iris.core.nms.container.IPackRepository;
import com.volmit.iris.util.io.IO; 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.tags.TagKey;
import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.biome.BiomeGenerationSettings; 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.CraftBlockStates;
import org.bukkit.craftbukkit.v1_19_R3.block.data.CraftBlockData; 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.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.inventory.CraftItemStack;
import org.bukkit.craftbukkit.v1_19_R3.scoreboard.CraftScoreboardManager;
import org.bukkit.entity.Dolphin; import org.bukkit.entity.Dolphin;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.ChunkGenerator; import org.bukkit.generator.ChunkGenerator;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
@ -847,6 +855,99 @@ public class NMSBinding implements INMSBinding {
return keys; 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<ServerPlayer>)playersField.get(player.getServer().getPlayerList());
var playersByName = (Map<String, ServerPlayer>)playersByNameField.get(player.getServer().getPlayerList());
var playersByUUID = (Map<UUID, ServerPlayer>)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 { private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
try { try {
for (Field f : clazz.getDeclaredFields()) { for (Field f : clazz.getDeclaredFields()) {

View File

@ -65,12 +65,20 @@ import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.Registries; import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.*; import net.minecraft.nbt.*;
import net.minecraft.nbt.Tag; 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.RegistryOps;
import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.PlayerAdvancements;
import net.minecraft.server.commands.data.BlockDataAccessor; import net.minecraft.server.commands.data.BlockDataAccessor;
import net.minecraft.server.level.ServerLevel; 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.tags.TagKey;
import net.minecraft.world.entity.EntityDimensions; import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.level.LevelReader; 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.CraftBlockStates;
import org.bukkit.craftbukkit.v1_20_R1.block.data.CraftBlockData; 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.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.inventory.CraftItemStack;
import org.bukkit.craftbukkit.v1_20_R1.scoreboard.CraftScoreboardManager;
import org.bukkit.entity.Dolphin; import org.bukkit.entity.Dolphin;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.ChunkGenerator; import org.bukkit.generator.ChunkGenerator;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
@ -120,11 +132,7 @@ import java.io.*;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.Iterator; import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Vector;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -839,6 +847,99 @@ public class NMSBinding implements INMSBinding {
return keys; 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<ServerPlayer>)playersField.get(player.getServer().getPlayerList());
var playersByName = (Map<String, ServerPlayer>)playersByNameField.get(player.getServer().getPlayerList());
var playersByUUID = (Map<UUID, ServerPlayer>)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 { private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
try { try {
for (Field f : clazz.getDeclaredFields()) { for (Field f : clazz.getDeclaredFields()) {

View File

@ -23,11 +23,7 @@ import java.io.*;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.Iterator; import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Vector;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -40,8 +36,17 @@ import com.volmit.iris.engine.object.IrisBiomeReplacement;
import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.J;
import net.minecraft.nbt.*; import net.minecraft.nbt.*;
import net.minecraft.nbt.Tag; 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.RegistryOps;
import net.minecraft.server.PlayerAdvancements;
import net.minecraft.server.commands.data.BlockDataAccessor; 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 net.minecraft.tags.TagKey;
import com.volmit.iris.core.nms.container.IPackRepository; import com.volmit.iris.core.nms.container.IPackRepository;
import com.volmit.iris.util.io.IO; 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.CraftBlockStates;
import org.bukkit.craftbukkit.v1_20_R2.block.data.CraftBlockData; 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.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.inventory.CraftItemStack;
import org.bukkit.craftbukkit.v1_20_R2.packs.CraftDataPackManager; 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.craftbukkit.v1_20_R2.util.CraftNamespacedKey;
import org.bukkit.entity.Dolphin; import org.bukkit.entity.Dolphin;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.ChunkGenerator; import org.bukkit.generator.ChunkGenerator;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
@ -841,6 +850,101 @@ public class NMSBinding implements INMSBinding {
return keys; 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<ServerPlayer>)playersField.get(player.getServer().getPlayerList());
var playersByName = (Map<String, ServerPlayer>)playersByNameField.get(player.getServer().getPlayerList());
var playersByUUID = (Map<UUID, ServerPlayer>)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 { private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
try { try {
for (Field f : clazz.getDeclaredFields()) { for (Field f : clazz.getDeclaredFields()) {

View File

@ -33,7 +33,6 @@ import java.util.stream.Collectors;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonNull; import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.mojang.datafixers.util.Pair; import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.Codec; import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps; import com.mojang.serialization.JsonOps;
@ -50,25 +49,25 @@ import com.volmit.iris.util.io.IO;
import it.unimi.dsi.fastutil.objects.Reference2IntMap; import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import net.bytebuddy.ByteBuddy; import net.bytebuddy.ByteBuddy;
import net.bytebuddy.asm.Advice; 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.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.bytebuddy.matcher.ElementMatchers;
import net.minecraft.core.IdMapper; import net.minecraft.core.IdMapper;
import net.minecraft.core.MappedRegistry; 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.resources.RegistryOps;
import net.minecraft.server.MinecraftServer; 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.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.util.GsonHelper;
import net.minecraft.world.RandomSequences; import net.minecraft.world.RandomSequences;
import net.minecraft.world.entity.MobCategory; 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.ChunkPos;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelHeightAccessor; 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.dimension.LevelStem;
import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.PrimaryLevelData; 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.core.nms.container.BiomeColor;
import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.J;
import net.minecraft.nbt.*; 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.CraftBlockState;
import org.bukkit.craftbukkit.v1_20_R3.block.CraftBlockStates; 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.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.inventory.CraftItemStack;
import org.bukkit.craftbukkit.v1_20_R3.scoreboard.CraftScoreboardManager;
import org.bukkit.craftbukkit.v1_20_R3.util.CraftNamespacedKey; import org.bukkit.craftbukkit.v1_20_R3.util.CraftNamespacedKey;
import org.bukkit.entity.Dolphin; import org.bukkit.entity.*;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityCategory;
import org.bukkit.entity.Mob;
import org.bukkit.event.entity.CreatureSpawnEvent; 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.BiomeProvider;
import org.bukkit.generator.ChunkGenerator; import org.bukkit.generator.ChunkGenerator;
import org.bukkit.inventory.ItemStack; 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.mantle.Mantle;
import com.volmit.iris.util.math.Vector3d; import com.volmit.iris.util.math.Vector3d;
import com.volmit.iris.util.matter.MatterBiomeInject; 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.NBTWorld;
import com.volmit.iris.util.nbt.mca.palette.*; import com.volmit.iris.util.nbt.mca.palette.*;
import com.volmit.iris.util.nbt.tag.CompoundTag; 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.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.EntityType; 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.Block;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunk;
import sun.misc.Unsafe;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments; import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
@ -893,6 +888,101 @@ public class NMSBinding implements INMSBinding {
return keys; 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<ServerPlayer>)playersField.get(player.getServer().getPlayerList());
var playersByName = (Map<String, ServerPlayer>)playersByNameField.get(player.getServer().getPlayerList());
var playersByUUID = (Map<UUID, ServerPlayer>)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 { private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
try { try {
for (Field f : clazz.getDeclaredFields()) { for (Field f : clazz.getDeclaredFields()) {

View File

@ -23,11 +23,7 @@ import java.io.*;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.Iterator; import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Vector;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -61,9 +57,18 @@ import com.volmit.iris.util.scheduling.J;
import net.minecraft.core.*; import net.minecraft.core.*;
import net.minecraft.core.Registry; import net.minecraft.core.Registry;
import net.minecraft.core.component.DataComponents; 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.resources.RegistryOps;
import net.minecraft.server.MinecraftServer; 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.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.util.GsonHelper;
import net.minecraft.world.RandomSequences; import net.minecraft.world.RandomSequences;
import net.minecraft.nbt.*; 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.CraftBlockType;
import org.bukkit.craftbukkit.v1_20_R4.block.data.CraftBlockData; 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.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.generator.CustomChunkGenerator;
import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack; 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.craftbukkit.v1_20_R4.util.CraftNamespacedKey;
import org.bukkit.entity.Dolphin; import org.bukkit.entity.Dolphin;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.ChunkGenerator; import org.bukkit.generator.ChunkGenerator;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
@ -935,6 +944,101 @@ public class NMSBinding implements INMSBinding {
return keys; 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<ServerPlayer>)playersField.get(player.getServer().getPlayerList());
var playersByName = (Map<String, ServerPlayer>)playersByNameField.get(player.getServer().getPlayerList());
var playersByUUID = (Map<UUID, ServerPlayer>)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 @Override
public IPackRepository getPackRepository() { public IPackRepository getPackRepository() {
return packRepository; return packRepository;

View File

@ -56,10 +56,19 @@ import com.volmit.iris.util.scheduling.J;
import net.minecraft.core.component.DataComponents; import net.minecraft.core.component.DataComponents;
import net.minecraft.nbt.*; import net.minecraft.nbt.*;
import net.minecraft.nbt.Tag; 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.RegistryOps;
import net.minecraft.server.PlayerAdvancements;
import net.minecraft.server.commands.data.BlockDataAccessor; import net.minecraft.server.commands.data.BlockDataAccessor;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ChunkMap; 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.tags.TagKey;
import net.minecraft.server.level.progress.ChunkProgressListener; import net.minecraft.server.level.progress.ChunkProgressListener;
import net.minecraft.util.GsonHelper; 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.CraftBlockStates;
import org.bukkit.craftbukkit.v1_21_R1.block.data.CraftBlockData; 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.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.inventory.CraftItemStack;
import org.bukkit.craftbukkit.v1_21_R1.scoreboard.CraftScoreboardManager;
import org.bukkit.craftbukkit.v1_21_R1.util.CraftNamespacedKey; import org.bukkit.craftbukkit.v1_21_R1.util.CraftNamespacedKey;
import org.bukkit.entity.Dolphin; import org.bukkit.entity.Dolphin;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.ChunkGenerator; import org.bukkit.generator.ChunkGenerator;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
@ -930,6 +943,101 @@ public class NMSBinding implements INMSBinding {
return keys; 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<ServerPlayer>)playersField.get(player.getServer().getPlayerList());
var playersByName = (Map<String, ServerPlayer>)playersByNameField.get(player.getServer().getPlayerList());
var playersByUUID = (Map<UUID, ServerPlayer>)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 @Override
public IPackRepository getPackRepository() { public IPackRepository getPackRepository() {
return packRepository; return packRepository;