/*
 *  Copyright 2025-2026 R3944Realms
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package top.r3944realms.eroticdungeongame.content.entity.npc;

import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.mojang.authlib.GameProfile;
import com.mojang.logging.LogUtils;
import com.mojang.serialization.Dynamic;
import io.netty.buffer.Unpooled;
import net.minecraft.ChatFormatting;
import net.minecraft.Util;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.core.LayeredRegistryAccess;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.RegistrySynchronization;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.network.Connection;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.*;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.PacketFlow;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.protocol.status.ServerStatus;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.RegistryLayer;
import net.minecraft.server.ServerScoreboard;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import net.minecraft.server.players.GameProfileCache;
import net.minecraft.server.players.PlayerList;
import net.minecraft.tags.TagNetworkSerialization;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.flag.FeatureFlags;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.biome.BiomeManager;
import net.minecraft.world.level.border.WorldBorder;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.storage.LevelData;
import net.minecraft.world.scores.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.OnDatapackSyncEvent;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import top.r3944realms.eroticdungeongame.core.network.EDGNetworkHandler;
import top.r3944realms.eroticdungeongame.core.network.NPCEmptyClientConnection;
import top.r3944realms.eroticdungeongame.core.network.toClient.NPCInfoUpdatePacket;
import top.r3944realms.eroticdungeongame.core.storage.NPCDataStorage;
import top.r3944realms.lib39.util.nbt.NBTReader;

import javax.annotation.Nullable;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.function.Predicate;

public class NPCPlayerList {
    private static final Logger LOGGER = LogUtils.getLogger();
    private static final int SEND_PLAYER_INFO_INTERVAL = 600;
    private final LayeredRegistryAccess<RegistryLayer> registries;
    private final RegistryAccess.Frozen synchronizedRegistries;
    private final NPCDataStorage npcIo;
    private final MinecraftServer server;
    private final List<NPCServerPlayer> npcs = new ArrayList<>();
    private final Map<UUID, NPCServerPlayer> npcsByUUID = Maps.newHashMap();
    private int sendAllNPCInfoIn;
    private final List<NPCServerPlayer> npcsView = java.util.Collections.unmodifiableList(npcs);
    public static final Component CHAT_FILTERED_FULL = Component.translatable("chat.filtered_full");
    public NPCPlayerList(MinecraftServer server, LayeredRegistryAccess<RegistryLayer> registries, NPCDataStorage npcDataStorage) {
        this.server = server;
        this.registries = registries;
        this.synchronizedRegistries = (new RegistryAccess.ImmutableRegistryAccess(RegistrySynchronization.networkedRegistries(registries))).freeze();
        this.npcIo = npcDataStorage;
    }
    public void placeNewNPC(Connection netManager, @NotNull NPCServerPlayer npc) {
        GameProfile gameprofile = npc.getGameProfile();
        GameProfileCache gameprofilecache = this.server.getProfileCache();
        String s;
        if (gameprofilecache != null) {
            Optional<GameProfile> optional = gameprofilecache.get(gameprofile.getId());
            s = optional.map(GameProfile::getName).orElse(gameprofile.getName());
            gameprofilecache.add(gameprofile);
        } else {
            s = gameprofile.getName();
        }
        CompoundTag compoundtag = this.load(npc);
        //noinspection deprecation
        ResourceKey<Level> resourcekey = compoundtag != null ? DimensionType.parseLegacy(new Dynamic<>(NbtOps.INSTANCE, compoundtag.get("Dimension"))).resultOrPartial(LOGGER::error).orElse(Level.OVERWORLD) : Level.OVERWORLD;
        ServerLevel serverlevel1 = getServerLevel(resourcekey);

        npc.setServerLevel(serverlevel1);
        String s1 = "local";
        //noinspection ConstantValue
        if (netManager.getRemoteAddress() != null) {
            s1 = net.minecraftforge.network.DualStackUtils.getAddressString(netManager.getRemoteAddress());
        }
        LOGGER.info("[NPC] {}[{}] logged in with entity id {} at ({}, {}, {})", npc.getName().getString(), s1, npc.getId(), npc.getX(), npc.getY(), npc.getZ());
        LevelData leveldata = serverlevel1.getLevelData();
        npc.loadGameTypes(compoundtag);
        ServerGamePacketListenerImpl servergamepacketlistenerimpl = new ServerGamePacketListenerImpl(this.server, netManager, npc);
        net.minecraftforge.network.NetworkHooks.sendMCRegistryPackets(netManager, "PLAY_TO_CLIENT");
        GameRules gamerules = serverlevel1.getGameRules();
        boolean flag = gamerules.getBoolean(GameRules.RULE_DO_IMMEDIATE_RESPAWN);
        boolean flag1 = gamerules.getBoolean(GameRules.RULE_REDUCEDDEBUGINFO);
        servergamepacketlistenerimpl.send(new ClientboundLoginPacket(npc.getId(), leveldata.isHardcore(), npc.gameMode.getGameModeForPlayer(), npc.gameMode.getPreviousGameModeForPlayer(), this.server.levelKeys(), this.synchronizedRegistries, serverlevel1.dimensionTypeId(), serverlevel1.dimension(), BiomeManager.obfuscateSeed(serverlevel1.getSeed()), 0, 16, 16, flag1, !flag, serverlevel1.isDebug(), serverlevel1.isFlat(), npc.getLastDeathLocation(), npc.getPortalCooldown()));
        servergamepacketlistenerimpl.send(new ClientboundUpdateEnabledFeaturesPacket(FeatureFlags.REGISTRY.toNames(serverlevel1.enabledFeatures())));
        servergamepacketlistenerimpl.send(new ClientboundCustomPayloadPacket(ClientboundCustomPayloadPacket.BRAND, (new FriendlyByteBuf(Unpooled.buffer())).writeUtf(this.getServer().getServerModName())));
        servergamepacketlistenerimpl.send(new ClientboundChangeDifficultyPacket(leveldata.getDifficulty(), leveldata.isDifficultyLocked()));
        servergamepacketlistenerimpl.send(new ClientboundPlayerAbilitiesPacket(npc.getAbilities()));
        servergamepacketlistenerimpl.send(new ClientboundSetCarriedItemPacket(npc.getInventory().selected));

        //数据同步事件（对于NPC类） 搞个继承类复用这个事件 (如果有模组用这个PlayerList搞其它事情可能会导致BUG)
        MinecraftForge.EVENT_BUS.post(new OnDatapackSyncEvent(this.toFakePlayerList(), npc));

        servergamepacketlistenerimpl.send(new ClientboundUpdateRecipesPacket(this.server.getRecipeManager().getRecipes()));
        servergamepacketlistenerimpl.send(new ClientboundUpdateTagsPacket(TagNetworkSerialization.serializeTagsToNetwork(this.registries)));
        this.sendPlayerPermissionLevel(npc);
        npc.getRecipeBook().sendInitialRecipeBook(npc);
        this.updateEntireScoreboard(serverlevel1.getScoreboard(), npc);
        this.server.invalidateStatus();
        MutableComponent mutablecomponent;
        if (npc.getGameProfile().getName().equalsIgnoreCase(s)) { //todo npc 语言
            mutablecomponent = Component.translatable("multiplayer.player.joined", npc.getDisplayName());
        } else {
            mutablecomponent = Component.translatable("multiplayer.player.joined.renamed", npc.getDisplayName(), s);
        }

        this.broadcastSystemMessage(mutablecomponent.withStyle(ChatFormatting.YELLOW), false);
        servergamepacketlistenerimpl.teleport(npc.getX(), npc.getY(), npc.getZ(), npc.getYRot(), npc.getXRot());
        ServerStatus serverstatus = this.server.getStatus();
        if (serverstatus != null) {
            npc.sendServerStatus(serverstatus);
        }
        // 实现网络传递
        EDGNetworkHandler.sendToPlayer(NPCInfoUpdatePacket.createNPCPlayerInitializing(this.npcs), npc);
        this.npcs.add(npc);
        this.npcsByUUID.put(npc.getUUID(), npc);
        EDGNetworkHandler.sendToAllPlayer(NPCInfoUpdatePacket.createNPCPlayerInitializing(List.of(npc)));
        this.sendLevelInfo(npc, serverlevel1);
        serverlevel1.addNewPlayer(npc);
        this.server.getCustomBossEvents().onPlayerConnect(npc);
        this.server.getServerResourcePack().ifPresent((serverResourcePackInfo) -> npc.sendTexturePack(serverResourcePackInfo.url(), serverResourcePackInfo.hash(), serverResourcePackInfo.isRequired(), serverResourcePackInfo.prompt()));

        for(MobEffectInstance mobeffectinstance : npc.getActiveEffects()) {
            servergamepacketlistenerimpl.send(new ClientboundUpdateMobEffectPacket(npc.getId(), mobeffectinstance));
        }

        if (compoundtag != null && compoundtag.contains("RootVehicle", 10)) {
            CompoundTag compoundtag1 = compoundtag.getCompound("RootVehicle");
            Entity entity1 = EntityType.loadEntityRecursive(compoundtag1.getCompound("Entity"), serverlevel1, (p_215603_) -> !serverlevel1.addWithUUID(p_215603_) ? null : p_215603_);
            if (entity1 != null) {
                UUID uuid;
                if (compoundtag1.hasUUID("Attach")) {
                    uuid = compoundtag1.getUUID("Attach");
                } else {
                    uuid = null;
                }

                if (entity1.getUUID().equals(uuid)) {
                    npc.startRiding(entity1, true);
                } else {
                    for(Entity entity : entity1.getIndirectPassengers()) {
                        if (entity.getUUID().equals(uuid)) {
                            npc.startRiding(entity, true);
                            break;
                        }
                    }
                }

                if (!npc.isPassenger()) {
                    LOGGER.warn("Couldn't reattach entity to player");
                    entity1.discard();

                    for(Entity entity2 : entity1.getIndirectPassengers()) {
                        entity2.discard();
                    }
                }
            }
        }

        npc.initInventoryMenu();
        net.minecraftforge.event.ForgeEventFactory.firePlayerLoggedIn(npc);
    }

    private @NotNull ServerLevel getServerLevel(ResourceKey<Level> resourcekey) {
        ServerLevel serverlevel = this.server.getLevel(resourcekey);
        ServerLevel serverlevel1;
        if (serverlevel == null) {
            LOGGER.warn("Unknown respawn dimension {}, defaulting to overworld", resourcekey);
            serverlevel1 = this.server.overworld();
        } else {
            serverlevel1 = serverlevel;
        }
        return serverlevel1;
    }

    /**
     * 加载所有已存在的 NPC（从磁盘）
     */
    public void loadAllNPCs() {
        String[] seenNPCs = this.npcIo.getSeenNPCs();
        LOGGER.info("Loading {} existing NPCs from disk", seenNPCs.length);

        for (String uuidStr : seenNPCs) {
            try {
                UUID uuid = UUID.fromString(uuidStr);
                if (loadNPC(uuid)) {
                    LOGGER.info("Loaded NPC {} from disk", uuidStr);
                }
            } catch (Exception e) {
                LOGGER.error("Failed to checkInit NPC with UUID: {}", uuidStr, e);
            }
        }
    }
    /**
     * 加载指定的 NPC
     * @param uuid NPC 的 UUID
     * @return 是否加载成功
     */
    public boolean loadNPC(UUID uuid) {
        // 检查是否已经加载
        if (this.npcsByUUID.containsKey(uuid)) {
            LOGGER.debug("NPC {} already loaded", uuid);
            return true;
        }

        try {
            NPCServerPlayer placeholder = new NPCServerPlayer(server, server.overworld(), new GameProfile(Util.NIL_UUID, "placeholder"));
            placeholder.setUUID(uuid);
            NPCServerPlayer npcServerPlayer = this.npcIo.load(uuid, this::createNPCFromData);
            if (npcServerPlayer != null) {
                placeNewNPC(new NPCEmptyClientConnection(PacketFlow.SERVERBOUND), npcServerPlayer);
                return true;
            }
        } catch (Exception e) {
            LOGGER.error("Error loading NPC with UUID: {}", uuid, e);
        }
        return false;
    }
    public @NotNull NPCServerPlayer createNPCFromData(CompoundTag npcData) {
        AtomicReference<String> npcName = new AtomicReference<>();
        AtomicReference<GameProfile> gameProfile = new AtomicReference<>();
        AtomicReference<ResourceKey<Level>> dimension = new AtomicReference<>(Level.OVERWORLD);
        //noinspection deprecation
        NBTReader.of(npcData)
                .string("NpcName", npcName::set)
                .compound("NpcGameProfile", data -> gameProfile.set(NbtUtils.readGameProfile(data)))
                .compound("Dimension", data ->  dimension.set(DimensionType.parseLegacy(new Dynamic<>(NbtOps.INSTANCE, data.get("Dimension")))
                        .resultOrPartial(LOGGER::error)
                        .orElse(Level.OVERWORLD)));
        ResourceKey<Level> levelResourceKey = dimension.get();
        ServerLevel serverlevel1 = getServerLevel(levelResourceKey);

        return new NPCServerPlayer(server, serverlevel1, gameProfile.get());
    }

    public void sendPlayerPermissionLevel(@NotNull ServerPlayer player) {
        GameProfile gameprofile = player.getGameProfile();
        int i = this.server.getProfilePermissions(gameprofile);
        this.sendPlayerPermissionLevel(player, i);
    }

    private void sendPlayerPermissionLevel(@NotNull ServerPlayer player, int permLevel) {
        byte b0;
        if (permLevel <= 0) {
            b0 = 24;
        } else if (permLevel >= 4) {
            b0 = 28;
        } else {
            b0 = (byte)(24 + permLevel);
        }

        player.connection.send(new ClientboundEntityEventPacket(player, b0));

        this.server.getCommands().sendCommands(player);
    }
    /**
     * Updates the time and weather for the given player to those of the given world
     */
    public void sendLevelInfo(@NotNull ServerPlayer player, @NotNull ServerLevel level) {
        WorldBorder worldborder = this.server.overworld().getWorldBorder();
        player.connection.send(new ClientboundInitializeBorderPacket(worldborder));
        player.connection.send(new ClientboundSetTimePacket(level.getGameTime(), level.getDayTime(), level.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)));
        player.connection.send(new ClientboundSetDefaultSpawnPositionPacket(level.getSharedSpawnPos(), level.getSharedSpawnAngle()));
        if (level.isRaining()) {
            player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 0.0F));
            player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, level.getRainLevel(1.0F)));
            player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, level.getThunderLevel(1.0F)));
        }

    }
    protected void updateEntireScoreboard(@NotNull ServerScoreboard scoreboard, ServerPlayer player) {
        Set<Objective> set = Sets.newHashSet();

        for(PlayerTeam playerteam : scoreboard.getPlayerTeams()) {
            player.connection.send(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(playerteam, true));
        }

        for(int i = 0; i < 19; ++i) {
            Objective objective = scoreboard.getDisplayObjective(i);
            if (objective != null && !set.contains(objective)) {
                for(Packet<?> packet : scoreboard.getStartTrackingPackets(objective)) {
                    player.connection.send(packet);
                }

                set.add(objective);
            }
        }

    }
    /**
     * Called during player login. Reads the player information from disk.
     */
    @Nullable
    public CompoundTag load(@NotNull NPCServerPlayer npc) {
        CompoundTag compoundtag = this.server.getWorldData().getLoadedPlayerTag();
        CompoundTag compoundtag1;
        if (this.server.isSingleplayerOwner(npc.getGameProfile()) && compoundtag != null) {
            compoundtag1 = compoundtag;
            npc.load(compoundtag);
            LOGGER.debug("loading single npc");
            // 应该不会有模组干这个吧？
            net.minecraftforge.event.ForgeEventFactory.firePlayerLoadingEvent(npc, this.npcIo.getNPCDataFolder(), npc.getUUID().toString());
        } else {
            compoundtag1 = this.npcIo.load(npc);
        }

        return compoundtag1;
    }
    public void broadcastSystemMessage(Component message, boolean bypassHiddenChat) {
        this.broadcastSystemMessage(message, (p_215639_) -> message, bypassHiddenChat);
    }
    public void saveAll() {
        for (NPCServerPlayer npc : this.npcs) {
            this.save(npc);
        }
    }

    protected void save(NPCServerPlayer npcServerPlayer) {
        this.npcIo.save(npcServerPlayer);
    }

    public void removeAll() {
        for (NPCServerPlayer npc : this.npcs) {
            npc.connection.disconnect(Component.translatable("multiplayer.disconnect.server_shutdown"));
        }
    }

    public void delete(NPCServerPlayer npc) {
        remove(npc);
        npcIo.deleteNPCData(npc);
    }

    public void remove(NPCServerPlayer npc) {
        net.minecraftforge.event.ForgeEventFactory.firePlayerLoggedOut(npc);
        ServerLevel serverlevel = npc.serverLevel();
        this.save(npc);
        if (npc.isPassenger()) {
            Entity entity = npc.getRootVehicle();
            if (entity.hasExactlyOnePlayerPassenger()) {
                LOGGER.debug("Removing player mount");
                npc.stopRiding();
                entity.getPassengersAndSelf().forEach((entity1) -> {
                    entity1.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER);
                });
            }
        }

        npc.unRide();
        serverlevel.removePlayerImmediately(npc, Entity.RemovalReason.UNLOADED_WITH_PLAYER);
        npc.getAdvancements().stopListening();
        this.npcs.remove(npc);
        this.server.getCustomBossEvents().onPlayerDisconnect(npc);
        UUID uuid = npc.getUUID();
        ServerPlayer serverplayer = this.npcsByUUID.get(uuid);
        if (serverplayer == npc) {
            this.npcsByUUID.remove(uuid);
        }
        this.broadcastAll(new ClientboundPlayerInfoRemovePacket(List.of(npc.getUUID())));
    }

    public void broadcastSystemMessage(Component serverMessage, Function<ServerPlayer, Component> playerMessageFactory, boolean bypassHiddenChat) {
        this.server.sendSystemMessage(serverMessage);
        PlayerList playerList = server.getPlayerList();
        for(ServerPlayer serverplayer : playerList.getPlayers()) {
            Component component = playerMessageFactory.apply(serverplayer);
            if (component != null) {
                serverplayer.sendSystemMessage(component, bypassHiddenChat);
            }
        }
        for(ServerPlayer serverplayer : this.npcs) {
            Component component = playerMessageFactory.apply(serverplayer);
            if (component != null) {
                serverplayer.sendSystemMessage(component, bypassHiddenChat);
            }
        }

    }

    public void tick() {
        if (++this.sendAllNPCInfoIn > SEND_PLAYER_INFO_INTERVAL) {
            EDGNetworkHandler.sendToAllPlayer(new NPCInfoUpdatePacket(EnumSet.of(NPCInfoUpdatePacket.Action.UPDATE_LATENCY), this.npcs));
            this.sendAllNPCInfoIn = 0;
        }

    }

    public void broadcastChatMessage(PlayerChatMessage message, @NotNull CommandSourceStack sender, ChatType.Bound boundChatType) {
        this.broadcastChatMessage(message, sender::shouldFilterMessageTo, sender.getPlayer(), boundChatType);
    }

    public void broadcastChatMessage(PlayerChatMessage message, ServerPlayer sender, ChatType.Bound boundChatType) {
        this.broadcastChatMessage(message, sender::shouldFilterMessageTo, sender, boundChatType);
    }

    private void broadcastChatMessage(PlayerChatMessage message, Predicate<ServerPlayer> shouldFilterMessageTo, @Nullable ServerPlayer sender, ChatType.Bound boundChatType) {
        boolean flag = this.verifyChatTrusted(message);
        this.server.logChatMessage(message.decoratedContent(), boundChatType, flag ? null : "Not Secure");
        OutgoingChatMessage outgoingchatmessage = OutgoingChatMessage.create(message);
        boolean flag1 = false;
        PlayerList playerList = server.getPlayerList();
        for(ServerPlayer serverplayer : playerList.getPlayers()) {
            boolean flag2 = shouldFilterMessageTo.test(serverplayer);
            serverplayer.sendChatMessage(outgoingchatmessage, flag2, boundChatType);
            flag1 |= flag2 && message.isFullyFiltered();
        }
        for(ServerPlayer serverplayer : this.npcs) {
            boolean flag2 = shouldFilterMessageTo.test(serverplayer);
            serverplayer.sendChatMessage(outgoingchatmessage, flag2, boundChatType);
            flag1 |= flag2 && message.isFullyFiltered();
        }

        if (flag1 && sender != null) {
            sender.sendSystemMessage(CHAT_FILTERED_FULL);
        }

    }
    public void broadcastAll(Packet<?> packet) {
        PlayerList playerList = server.getPlayerList();
        for(ServerPlayer serverplayer : playerList.getPlayers()) {
            serverplayer.connection.send(packet);
        }
        for(ServerPlayer serverplayer : this.npcs) {
            serverplayer.connection.send(packet);
        }

    }

    public void broadcastAll(Packet<?> packet, ResourceKey<Level> dimension) {
        PlayerList playerList = server.getPlayerList();
        for(ServerPlayer serverplayer : playerList.getPlayers()) {
            if (serverplayer.level().dimension() == dimension) {
                serverplayer.connection.send(packet);
            }
        }
        for(ServerPlayer serverplayer : this.npcs) {
            if (serverplayer.level().dimension() == dimension) {
                serverplayer.connection.send(packet);
            }
        }

    }

    private boolean verifyChatTrusted(@NotNull PlayerChatMessage message) {
        return message.hasSignature() && !message.hasExpiredServer(Instant.now());
    }

    public List<NPCServerPlayer> getPlayers() {
        return this.npcsView; //Unmodifiable view
    }

    @Nullable
    public ServerPlayer getNPC(UUID npcUUID) {
        return this.npcsByUUID.get(npcUUID);
    }
    public void reloadResources() {
        this.broadcastAll(new ClientboundUpdateTagsPacket(TagNetworkSerialization.serializeTagsToNetwork(this.registries)));
        ClientboundUpdateRecipesPacket clientboundupdaterecipespacket = new ClientboundUpdateRecipesPacket(this.server.getRecipeManager().getRecipes());
        for(ServerPlayer serverplayer : this.npcs) {
            serverplayer.connection.send(clientboundupdaterecipespacket);
            serverplayer.getRecipeBook().sendInitialRecipeBook(serverplayer);
        }

    }

    public MinecraftServer getServer() {
        return this.server;
    }

    public @NotNull FakePlayerList toFakePlayerList() {
        return new FakePlayerList(this.server, this.registries, this.npcs, this.npcsByUUID);
    }
}
