package net.minecraft.server;

import org.apache.logging.log4j.LogManager;
import java.util.concurrent.CompletionStage;
import net.minecraft.util.profiling.ProfileResults;
import java.lang.management.ThreadMXBean;
import java.util.Comparator;
import java.lang.management.ThreadInfo;
import java.lang.management.ManagementFactory;
import com.google.common.base.Splitter;
import java.io.Writer;
import java.nio.file.OpenOption;
import net.minecraft.resources.ResourceLocation;
import java.nio.file.Files;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.Path;
import net.minecraft.server.players.ServerOpListEntry;
import net.minecraft.world.level.storage.loot.PredicateManager;
import net.minecraft.world.level.storage.loot.LootTables;
import net.minecraft.tags.TagManager;
import net.minecraft.world.item.crafting.RecipeManager;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.Vec2;
import net.minecraft.core.Vec3i;
import net.minecraft.world.phys.Vec3;
import net.minecraft.server.players.UserWhiteList;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.commands.CommandSourceStack;
import com.google.common.collect.Sets;
import net.minecraft.world.level.DataPackConfig;
import net.minecraft.server.packs.PackResources;
import net.minecraft.commands.Commands;
import java.util.stream.Collector;
import java.util.function.Predicate;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.Collection;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.storage.LevelData;
import net.minecraft.network.protocol.game.ClientboundChangeDifficultyPacket;
import java.util.function.Consumer;
import java.util.UUID;
import java.util.Optional;
import java.util.Set;
import net.minecraft.gametest.framework.GameTestTicker;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientboundSetTimePacket;
import net.minecraft.world.level.GameRules;
import java.util.Collections;
import java.util.Arrays;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Mth;
import com.mojang.authlib.GameProfile;
import java.util.function.BooleanSupplier;
import java.nio.ByteBuffer;
import java.awt.image.BufferedImage;
import io.netty.buffer.ByteBuf;
import java.util.Base64;
import java.io.OutputStream;
import java.awt.image.RenderedImage;
import io.netty.buffer.ByteBufOutputStream;
import org.apache.commons.lang3.Validate;
import javax.imageio.ImageIO;
import io.netty.buffer.Unpooled;
import java.util.Date;
import java.text.SimpleDateFormat;
import net.minecraft.util.profiling.SingleTickProfiler;
import net.minecraft.SharedConstants;
import net.minecraft.network.chat.TextComponent;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import net.minecraft.world.level.storage.LevelResource;
import it.unimi.dsi.fastutil.longs.LongIterator;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.world.level.ForcedChunksSavedData;
import net.minecraft.util.Unit;
import net.minecraft.server.level.TicketType;
import net.minecraft.world.level.GameType;
import net.minecraft.world.Difficulty;
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.server.level.PlayerRespawnLogic;
import net.minecraft.world.level.block.Block;
import net.minecraft.tags.BlockTags;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.core.BlockPos;
import java.util.Iterator;
import net.minecraft.world.level.border.WorldBorder;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.core.MappedRegistry;
import net.minecraft.world.level.border.BorderChangeListener;
import net.minecraft.world.level.storage.ServerLevelData;
import net.minecraft.world.level.storage.DerivedLevelData;
import net.minecraft.core.Registry;
import net.minecraft.ReportedException;
import net.minecraft.CrashReport;
import net.minecraft.world.level.levelgen.WorldGenSettings;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.CustomSpawner;
import com.google.common.collect.ImmutableList;
import net.minecraft.world.entity.npc.WanderingTraderSpawner;
import net.minecraft.world.entity.ai.village.VillageSiege;
import net.minecraft.world.entity.npc.CatSpawner;
import net.minecraft.world.level.levelgen.PatrolSpawner;
import net.minecraft.world.level.levelgen.PhantomSpawner;
import net.minecraft.world.level.biome.BiomeManager;
import net.minecraft.server.level.progress.ChunkProgressListener;
import net.minecraft.network.chat.Component;
import net.minecraft.util.ProgressListener;
import java.io.IOException;
import net.minecraft.world.level.saveddata.SavedData;
import net.minecraft.world.level.saveddata.SaveDataDirtyRunnable;
import net.minecraft.world.scores.Scoreboard;
import net.minecraft.world.scores.ScoreboardSaveData;
import net.minecraft.world.level.storage.DimensionDataStorage;
import com.google.common.collect.Maps;
import net.minecraft.util.profiling.InactiveProfiler;
import com.google.common.collect.Lists;
import net.minecraft.Util;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import net.minecraft.world.level.storage.WorldData;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureManager;
import java.util.concurrent.Executor;
import net.minecraft.util.FrameTimer;
import net.minecraft.server.bossevents.CustomBossEvents;
import net.minecraft.world.level.storage.CommandStorage;
import net.minecraft.server.packs.repository.Pack;
import net.minecraft.server.packs.repository.PackRepository;
import net.minecraft.server.players.GameProfileCache;
import com.mojang.authlib.GameProfileRepository;
import com.mojang.authlib.minecraft.MinecraftSessionService;
import java.security.KeyPair;
import javax.annotation.Nullable;
import java.net.Proxy;
import net.minecraft.server.players.PlayerList;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
import net.minecraft.resources.ResourceKey;
import java.util.Map;
import net.minecraft.core.RegistryAccess;
import com.mojang.datafixers.DataFixer;
import java.util.Random;
import net.minecraft.network.protocol.status.ServerStatus;
import net.minecraft.server.level.progress.ChunkProgressListenerFactory;
import net.minecraft.server.network.ServerConnectionListener;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.util.profiling.ContinuousProfiler;
import java.util.List;
import net.minecraft.world.Snooper;
import net.minecraft.world.level.storage.PlayerDataStorage;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.LevelSettings;
import java.io.File;
import org.apache.logging.log4j.Logger;
import net.minecraft.commands.CommandSource;
import net.minecraft.world.SnooperPopulator;
import net.minecraft.util.thread.ReentrantBlockableEventLoop;

public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTask> implements SnooperPopulator, CommandSource, AutoCloseable {
    private static final Logger LOGGER;
    public static final File USERID_CACHE_FILE;
    public static final LevelSettings DEMO_SETTINGS;
    protected final LevelStorageSource.LevelStorageAccess storageSource;
    protected final PlayerDataStorage playerDataStorage;
    private final Snooper snooper;
    private final List<Runnable> tickables;
    private ContinuousProfiler continousProfiler;
    private ProfilerFiller profiler;
    private final ServerConnectionListener connection;
    private final ChunkProgressListenerFactory progressListenerFactory;
    private final ServerStatus status;
    private final Random random;
    private final DataFixer fixerUpper;
    private String localIp;
    private int port;
    protected final RegistryAccess.RegistryHolder registryHolder;
    private final Map<ResourceKey<Level>, ServerLevel> levels;
    private PlayerList playerList;
    private volatile boolean running;
    private boolean stopped;
    private int tickCount;
    protected final Proxy proxy;
    private boolean onlineMode;
    private boolean preventProxyConnections;
    private boolean pvp;
    private boolean allowFlight;
    @Nullable
    private String motd;
    private int maxBuildHeight;
    private int playerIdleTimeout;
    public final long[] tickTimes;
    @Nullable
    private KeyPair keyPair;
    @Nullable
    private String singleplayerName;
    private boolean isDemo;
    private String resourcePack;
    private String resourcePackHash;
    private volatile boolean isReady;
    private long lastOverloadWarning;
    private boolean delayProfilerStart;
    private boolean forceGameType;
    private final MinecraftSessionService sessionService;
    private final GameProfileRepository profileRepository;
    private final GameProfileCache profileCache;
    private long lastServerStatus;
    private final Thread serverThread;
    private long nextTickTime;
    private long delayedTasksMaxNextTickTime;
    private boolean mayHaveDelayedTasks;
    private boolean hasWorldScreenshot;
    private final PackRepository<Pack> packRepository;
    private final ServerScoreboard scoreboard;
    @Nullable
    private CommandStorage commandStorage;
    private final CustomBossEvents customBossEvents;
    private final ServerFunctionManager functionManager;
    private final FrameTimer frameTimer;
    private boolean enforceWhitelist;
    private float averageTickTime;
    private final Executor executor;
    @Nullable
    private String serverId;
    private ServerResources resources;
    private final StructureManager structureManager;
    protected final WorldData worldData;
    
    public static <S extends MinecraftServer> S spin(final Function<Thread, S> function) {
        final AtomicReference<S> atomicReference2 = new AtomicReference<S>();
        final Thread thread3 = new Thread(() -> atomicReference2.get().runServer(), "Server thread");
        thread3.setUncaughtExceptionHandler((thread, throwable) -> MinecraftServer.LOGGER.error(throwable));
        final S minecraftServer4 = function.apply(thread3);
        atomicReference2.set(minecraftServer4);
        thread3.start();
        return minecraftServer4;
    }
    
    public MinecraftServer(final Thread thread, final RegistryAccess.RegistryHolder a, final LevelStorageSource.LevelStorageAccess a, final WorldData dal, final PackRepository<Pack> aar, final Proxy proxy, final DataFixer dataFixer, final ServerResources uw, final MinecraftSessionService minecraftSessionService, final GameProfileRepository gameProfileRepository, final GameProfileCache abl, final ChunkProgressListenerFactory zn) {
        super("Server");
        this.snooper = new Snooper("server", this, Util.getMillis());
        this.tickables = Lists.newArrayList();
        this.continousProfiler = new ContinuousProfiler(Util.timeSource, this::getTickCount);
        this.profiler = InactiveProfiler.INSTANCE;
        this.status = new ServerStatus();
        this.random = new Random();
        this.port = -1;
        this.levels = Maps.newLinkedHashMap();
        this.running = true;
        this.tickTimes = new long[100];
        this.resourcePack = "";
        this.resourcePackHash = "";
        this.nextTickTime = Util.getMillis();
        this.scoreboard = new ServerScoreboard(this);
        this.customBossEvents = new CustomBossEvents();
        this.frameTimer = new FrameTimer();
        this.registryHolder = a;
        this.worldData = dal;
        this.proxy = proxy;
        this.packRepository = aar;
        this.resources = uw;
        this.sessionService = minecraftSessionService;
        this.profileRepository = gameProfileRepository;
        this.profileCache = abl;
        this.connection = new ServerConnectionListener(this);
        this.progressListenerFactory = zn;
        this.storageSource = a;
        this.playerDataStorage = a.createPlayerStorage();
        this.fixerUpper = dataFixer;
        this.functionManager = new ServerFunctionManager(this, uw.getFunctionLibrary());
        this.structureManager = new StructureManager(uw.getResourceManager(), a, dataFixer);
        this.serverThread = thread;
        this.executor = Util.backgroundExecutor();
    }
    
    private void readScoreboard(final DimensionDataStorage daa) {
        final ScoreboardSaveData dfn3 = daa.<ScoreboardSaveData>computeIfAbsent(ScoreboardSaveData::new, "scoreboard");
        dfn3.setScoreboard(this.getScoreboard());
        this.getScoreboard().addDirtyListener(new SaveDataDirtyRunnable(dfn3));
    }
    
    protected abstract boolean initServer() throws IOException;
    
    public static void convertFromRegionFormatIfNeeded(final LevelStorageSource.LevelStorageAccess a) {
        if (a.requiresConversion()) {
            MinecraftServer.LOGGER.info("Converting map!");
            a.convertLevel(new ProgressListener() {
                private long timeStamp = Util.getMillis();
                
                @Override
                public void progressStartNoAbort(final Component mr) {
                }
                
                @Override
                public void progressStart(final Component mr) {
                }
                
                @Override
                public void progressStagePercentage(final int integer) {
                    if (Util.getMillis() - this.timeStamp >= 1000L) {
                        this.timeStamp = Util.getMillis();
                        MinecraftServer.LOGGER.info("Converting... {}%", integer);
                    }
                }
                
                @Override
                public void stop() {
                }
                
                @Override
                public void progressStage(final Component mr) {
                }
            });
        }
    }
    
    protected void loadLevel() {
        this.detectBundledResources();
        this.worldData.setModdedInfo(this.getServerModName(), this.getModdedStatus().isPresent());
        final ChunkProgressListener zm2 = this.progressListenerFactory.create(11);
        this.createLevels(zm2);
        this.forceDifficulty();
        this.prepareLevels(zm2);
    }
    
    protected void forceDifficulty() {
    }
    
    protected void createLevels(final ChunkProgressListener zm) {
        final ServerLevelData dak3 = this.worldData.overworldData();
        final WorldGenSettings cix4 = this.worldData.worldGenSettings();
        final boolean boolean5 = cix4.isDebug();
        final long long6 = cix4.seed();
        final long long7 = BiomeManager.obfuscateSeed(long6);
        final List<CustomSpawner> list10 = ImmutableList.<CustomSpawner>of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(dak3));
        final MappedRegistry<LevelStem> gh11 = cix4.dimensions();
        final LevelStem cig13 = gh11.get(LevelStem.OVERWORLD);
        DimensionType cif14;
        ChunkGenerator cha12;
        if (cig13 == null) {
            cif14 = DimensionType.defaultOverworld();
            cha12 = WorldGenSettings.makeDefaultOverworld(new Random().nextLong());
        }
        else {
            cif14 = cig13.type();
            cha12 = cig13.generator();
        }
        final Object o;
        final Object obj;
        final ResourceKey<DimensionType> ug15 = this.registryHolder.dimensionTypes().getResourceKey(cif14).<Throwable>orElseThrow(() -> {
            new IllegalStateException("Unregistered dimension type: " + obj);
            return o;
        });
        final ServerLevel zd16 = new ServerLevel(this, this.executor, this.storageSource, dak3, Level.OVERWORLD, ug15, cif14, zm, cha12, boolean5, long7, list10, true);
        this.levels.put(Level.OVERWORLD, zd16);
        final DimensionDataStorage daa17 = zd16.getDataStorage();
        this.readScoreboard(daa17);
        this.commandStorage = new CommandStorage(daa17);
        final WorldBorder cgw18 = zd16.getWorldBorder();
        cgw18.applySettings(dak3.getWorldBorder());
        if (!dak3.isInitialized()) {
            try {
                setInitialSpawn(zd16, dak3, cix4.generateBonusChest(), boolean5, true);
                dak3.setInitialized(true);
                if (boolean5) {
                    this.setupDebugLevel(this.worldData);
                }
            }
            catch (Throwable throwable19) {
                final CrashReport j20 = CrashReport.forThrowable(throwable19, "Exception initializing level");
                try {
                    zd16.fillReportDetails(j20);
                }
                catch (Throwable t) {}
                throw new ReportedException(j20);
            }
            dak3.setInitialized(true);
        }
        this.getPlayerList().setLevel(zd16);
        if (this.worldData.getCustomBossEvents() != null) {
            this.getCustomBossEvents().load(this.worldData.getCustomBossEvents());
        }
        for (final Map.Entry<ResourceKey<LevelStem>, LevelStem> entry20 : gh11.entrySet()) {
            final ResourceKey<LevelStem> ug16 = entry20.getKey();
            if (ug16 == LevelStem.OVERWORLD) {
                continue;
            }
            final ResourceKey<Level> ug17 = ResourceKey.<Level>create(Registry.DIMENSION_REGISTRY, ug16.location());
            final DimensionType cif15 = entry20.getValue().type();
            final Object o2;
            final Object obj2;
            final ResourceKey<DimensionType> ug18 = this.registryHolder.dimensionTypes().getResourceKey(cif15).<Throwable>orElseThrow(() -> {
                new IllegalStateException("Unregistered dimension type: " + obj2);
                return o2;
            });
            final ChunkGenerator cha13 = entry20.getValue().generator();
            final DerivedLevelData czz26 = new DerivedLevelData(this.worldData, dak3);
            final ServerLevel zd17 = new ServerLevel(this, this.executor, this.storageSource, czz26, ug17, ug18, cif15, zm, cha13, boolean5, long7, ImmutableList.of(), false);
            cgw18.addListener(new BorderChangeListener.DelegateBorderChangeListener(zd17.getWorldBorder()));
            this.levels.put(ug17, zd17);
        }
    }
    
    private static void setInitialSpawn(final ServerLevel zd, final ServerLevelData dak, final boolean boolean3, final boolean boolean4, final boolean boolean5) {
        final ChunkGenerator cha6 = zd.getChunkSource().getGenerator();
        if (!boolean5) {
            dak.setSpawn(BlockPos.ZERO.above(cha6.getSpawnHeight()));
            return;
        }
        if (boolean4) {
            dak.setSpawn(BlockPos.ZERO.above());
            return;
        }
        final BiomeSource brh7 = cha6.getBiomeSource();
        final List<Biome> list8 = brh7.getPlayerSpawnBiomes();
        final Random random9 = new Random(zd.getSeed());
        final BlockPos fu10 = brh7.findBiomeHorizontal(0, zd.getSeaLevel(), 0, 256, list8, random9);
        final ChunkPos bph11 = (fu10 == null) ? new ChunkPos(0, 0) : new ChunkPos(fu10);
        if (fu10 == null) {
            MinecraftServer.LOGGER.warn("Unable to find spawn biome");
        }
        boolean boolean6 = false;
        for (final Block bvr14 : BlockTags.VALID_SPAWN.getValues()) {
            if (brh7.getSurfaceBlocks().contains(bvr14.defaultBlockState())) {
                boolean6 = true;
                break;
            }
        }
        dak.setSpawn(bph11.getWorldPosition().offset(8, cha6.getSpawnHeight(), 8));
        int integer13 = 0;
        int integer14 = 0;
        int integer15 = 0;
        int integer16 = -1;
        final int integer17 = 32;
        for (int integer18 = 0; integer18 < 1024; ++integer18) {
            if (integer13 > -16 && integer13 <= 16 && integer14 > -16 && integer14 <= 16) {
                final BlockPos fu11 = PlayerRespawnLogic.getSpawnPosInChunk(zd, new ChunkPos(bph11.x + integer13, bph11.z + integer14), boolean6);
                if (fu11 != null) {
                    dak.setSpawn(fu11);
                    break;
                }
            }
            if (integer13 == integer14 || (integer13 < 0 && integer13 == -integer14) || (integer13 > 0 && integer13 == 1 - integer14)) {
                final int integer19 = integer15;
                integer15 = -integer16;
                integer16 = integer19;
            }
            integer13 += integer15;
            integer14 += integer16;
        }
        if (boolean3) {
            final ConfiguredFeature<?, ?> ckb18 = Feature.BONUS_CHEST.configured(FeatureConfiguration.NONE);
            ckb18.place(zd, zd.structureFeatureManager(), cha6, zd.random, new BlockPos(dak.getXSpawn(), dak.getYSpawn(), dak.getZSpawn()));
        }
    }
    
    private void setupDebugLevel(final WorldData dal) {
        dal.setDifficulty(Difficulty.PEACEFUL);
        dal.setDifficultyLocked(true);
        final ServerLevelData dak3 = dal.overworldData();
        dak3.setRaining(false);
        dak3.setThundering(false);
        dak3.setClearWeatherTime(1000000000);
        dak3.setDayTime(6000L);
        dak3.setGameType(GameType.SPECTATOR);
    }
    
    private void prepareLevels(final ChunkProgressListener zm) {
        final ServerLevel zd3 = this.overworld();
        MinecraftServer.LOGGER.info("Preparing start region for dimension {}", zd3.dimension().location());
        final BlockPos fu4 = zd3.getSharedSpawnPos();
        zm.updateSpawnPos(new ChunkPos(fu4));
        final ServerChunkCache zb5 = zd3.getChunkSource();
        zb5.getLightEngine().setTaskPerBatch(500);
        this.nextTickTime = Util.getMillis();
        zb5.<Unit>addRegionTicket(TicketType.START, new ChunkPos(fu4), 11, Unit.INSTANCE);
        while (zb5.getTickingGenerated() != 441) {
            this.nextTickTime = Util.getMillis() + 10L;
            this.waitUntilNextTick();
        }
        this.nextTickTime = Util.getMillis() + 10L;
        this.waitUntilNextTick();
        for (final ServerLevel zd4 : this.levels.values()) {
            final ForcedChunksSavedData bpw8 = zd4.getDataStorage().<ForcedChunksSavedData>get(ForcedChunksSavedData::new, "chunks");
            if (bpw8 != null) {
                final LongIterator longIterator9 = bpw8.getChunks().iterator();
                while (longIterator9.hasNext()) {
                    final long long10 = longIterator9.nextLong();
                    final ChunkPos bph12 = new ChunkPos(long10);
                    zd4.getChunkSource().updateChunkForced(bph12, true);
                }
            }
        }
        this.nextTickTime = Util.getMillis() + 10L;
        this.waitUntilNextTick();
        zm.stop();
        zb5.getLightEngine().setTaskPerBatch(5);
        this.updateMobSpawningFlags();
    }
    
    protected void detectBundledResources() {
        final File file2 = this.storageSource.getLevelPath(LevelResource.MAP_RESOURCE_FILE).toFile();
        if (file2.isFile()) {
            final String string3 = this.storageSource.getLevelId();
            try {
                this.setResourcePack("level://" + URLEncoder.encode(string3, StandardCharsets.UTF_8.toString()) + "/" + "resources.zip", "");
            }
            catch (UnsupportedEncodingException unsupportedEncodingException4) {
                MinecraftServer.LOGGER.warn("Something went wrong url encoding {}", string3);
            }
        }
    }
    
    public GameType getDefaultGameType() {
        return this.worldData.getGameType();
    }
    
    public boolean isHardcore() {
        return this.worldData.isHardcore();
    }
    
    public abstract int getOperatorUserPermissionLevel();
    
    public abstract int getFunctionCompilationLevel();
    
    public abstract boolean shouldRconBroadcast();
    
    public boolean saveAllChunks(final boolean boolean1, final boolean boolean2, final boolean boolean3) {
        boolean boolean4 = false;
        for (final ServerLevel zd7 : this.getAllLevels()) {
            if (!boolean1) {
                MinecraftServer.LOGGER.info("Saving chunks for level '{}'/{}", zd7, zd7.dimension().location());
            }
            zd7.save(null, boolean2, zd7.noSave && !boolean3);
            boolean4 = true;
        }
        final ServerLevel zd8 = this.overworld();
        final ServerLevelData dak7 = this.worldData.overworldData();
        dak7.setWorldBorder(zd8.getWorldBorder().createSettings());
        this.worldData.setCustomBossEvents(this.getCustomBossEvents().save());
        this.storageSource.saveDataTag(this.registryHolder, this.worldData, this.getPlayerList().getSingleplayerData());
        return boolean4;
    }
    
    @Override
    public void close() {
        this.stopServer();
    }
    
    protected void stopServer() {
        MinecraftServer.LOGGER.info("Stopping server");
        if (this.getConnection() != null) {
            this.getConnection().stop();
        }
        if (this.playerList != null) {
            MinecraftServer.LOGGER.info("Saving players");
            this.playerList.saveAll();
            this.playerList.removeAll();
        }
        MinecraftServer.LOGGER.info("Saving worlds");
        for (final ServerLevel zd3 : this.getAllLevels()) {
            if (zd3 != null) {
                zd3.noSave = false;
            }
        }
        this.saveAllChunks(false, true, false);
        for (final ServerLevel zd3 : this.getAllLevels()) {
            if (zd3 != null) {
                try {
                    zd3.close();
                }
                catch (IOException iOException4) {
                    MinecraftServer.LOGGER.error("Exception closing the level", (Throwable)iOException4);
                }
            }
        }
        if (this.snooper.isStarted()) {
            this.snooper.interrupt();
        }
        this.resources.close();
        try {
            this.storageSource.close();
        }
        catch (IOException iOException5) {
            MinecraftServer.LOGGER.error("Failed to unlock level {}", this.storageSource.getLevelId(), iOException5);
        }
    }
    
    public String getLocalIp() {
        return this.localIp;
    }
    
    public void setLocalIp(final String string) {
        this.localIp = string;
    }
    
    public boolean isRunning() {
        return this.running;
    }
    
    public void halt(final boolean boolean1) {
        this.running = false;
        if (boolean1) {
            try {
                this.serverThread.join();
            }
            catch (InterruptedException interruptedException3) {
                MinecraftServer.LOGGER.error("Error while shutting down", (Throwable)interruptedException3);
            }
        }
    }
    
    protected void runServer() {
        try {
            if (this.initServer()) {
                this.nextTickTime = Util.getMillis();
                this.status.setDescription(new TextComponent(this.motd));
                this.status.setVersion(new ServerStatus.Version(SharedConstants.getCurrentVersion().getName(), SharedConstants.getCurrentVersion().getProtocolVersion()));
                this.updateStatusIcon(this.status);
                while (this.running) {
                    final long long2 = Util.getMillis() - this.nextTickTime;
                    if (long2 > 2000L && this.nextTickTime - this.lastOverloadWarning >= 15000L) {
                        final long long3 = long2 / 50L;
                        MinecraftServer.LOGGER.warn("Can't keep up! Is the server overloaded? Running {}ms or {} ticks behind", long2, long3);
                        this.nextTickTime += long3 * 50L;
                        this.lastOverloadWarning = this.nextTickTime;
                    }
                    this.nextTickTime += 50L;
                    final SingleTickProfiler aml4 = SingleTickProfiler.createTickProfiler("Server");
                    this.startProfilerTick(aml4);
                    this.profiler.startTick();
                    this.profiler.push("tick");
                    this.tickServer(this::haveTime);
                    this.profiler.popPush("nextTickWait");
                    this.mayHaveDelayedTasks = true;
                    this.delayedTasksMaxNextTickTime = Math.max(Util.getMillis() + 50L, this.nextTickTime);
                    this.waitUntilNextTick();
                    this.profiler.pop();
                    this.profiler.endTick();
                    this.endProfilerTick(aml4);
                    this.isReady = true;
                }
            }
            else {
                this.onServerCrash(null);
            }
        }
        catch (Throwable throwable2) {
            MinecraftServer.LOGGER.error("Encountered an unexpected exception", throwable2);
            CrashReport j3;
            if (throwable2 instanceof ReportedException) {
                j3 = this.fillReport(((ReportedException)throwable2).getReport());
            }
            else {
                j3 = this.fillReport(new CrashReport("Exception in server tick loop", throwable2));
            }
            final File file4 = new File(new File(this.getServerDirectory(), "crash-reports"), "crash-" + new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss").format(new Date()) + "-server.txt");
            if (j3.saveToFile(file4)) {
                MinecraftServer.LOGGER.error("This crash report has been saved to: {}", file4.getAbsolutePath());
            }
            else {
                MinecraftServer.LOGGER.error("We were unable to save this crash report to disk.");
            }
            this.onServerCrash(j3);
            try {
                this.stopped = true;
                this.stopServer();
            }
            catch (Throwable throwable2) {
                MinecraftServer.LOGGER.error("Exception stopping the server", throwable2);
            }
            finally {
                this.onServerExit();
            }
        }
        finally {
            try {
                this.stopped = true;
                this.stopServer();
            }
            catch (Throwable throwable3) {
                MinecraftServer.LOGGER.error("Exception stopping the server", throwable3);
                this.onServerExit();
            }
            finally {
                this.onServerExit();
            }
        }
    }
    
    private boolean haveTime() {
        return this.runningTask() || Util.getMillis() < (this.mayHaveDelayedTasks ? this.delayedTasksMaxNextTickTime : this.nextTickTime);
    }
    
    protected void waitUntilNextTick() {
        this.runAllTasks();
        this.managedBlock(() -> !this.haveTime());
    }
    
    @Override
    protected TickTask wrapRunnable(final Runnable runnable) {
        return new TickTask(this.tickCount, runnable);
    }
    
    @Override
    protected boolean shouldRun(final TickTask uy) {
        return uy.getTick() + 3 < this.tickCount || this.haveTime();
    }
    
    public boolean pollTask() {
        final boolean boolean2 = this.pollTaskInternal();
        return this.mayHaveDelayedTasks = boolean2;
    }
    
    private boolean pollTaskInternal() {
        if (super.pollTask()) {
            return true;
        }
        if (this.haveTime()) {
            for (final ServerLevel zd3 : this.getAllLevels()) {
                if (zd3.getChunkSource().pollTask()) {
                    return true;
                }
            }
        }
        return false;
    }
    
    @Override
    protected void doRunTask(final TickTask uy) {
        this.getProfiler().incrementCounter("runTask");
        super.doRunTask(uy);
    }
    
    private void updateStatusIcon(final ServerStatus tl) {
        File file3 = this.getFile("server-icon.png");
        if (!file3.exists()) {
            file3 = this.storageSource.getIconFile();
        }
        if (file3.isFile()) {
            final ByteBuf byteBuf4 = Unpooled.buffer();
            try {
                final BufferedImage bufferedImage5 = ImageIO.read(file3);
                Validate.validState(bufferedImage5.getWidth() == 64, "Must be 64 pixels wide", new Object[0]);
                Validate.validState(bufferedImage5.getHeight() == 64, "Must be 64 pixels high", new Object[0]);
                ImageIO.write(bufferedImage5, "PNG", (OutputStream)new ByteBufOutputStream(byteBuf4));
                final ByteBuffer byteBuffer6 = Base64.getEncoder().encode(byteBuf4.nioBuffer());
                tl.setFavicon("data:image/png;base64," + StandardCharsets.UTF_8.decode(byteBuffer6));
            }
            catch (Exception exception5) {
                MinecraftServer.LOGGER.error("Couldn't load server icon", (Throwable)exception5);
            }
            finally {
                byteBuf4.release();
            }
        }
    }
    
    public boolean hasWorldScreenshot() {
        return this.hasWorldScreenshot = (this.hasWorldScreenshot || this.getWorldScreenshotFile().isFile());
    }
    
    public File getWorldScreenshotFile() {
        return this.storageSource.getIconFile();
    }
    
    public File getServerDirectory() {
        return new File(".");
    }
    
    protected void onServerCrash(final CrashReport j) {
    }
    
    protected void onServerExit() {
    }
    
    protected void tickServer(final BooleanSupplier booleanSupplier) {
        final long long3 = Util.getNanos();
        ++this.tickCount;
        this.tickChildren(booleanSupplier);
        if (long3 - this.lastServerStatus >= 5000000000L) {
            this.lastServerStatus = long3;
            this.status.setPlayers(new ServerStatus.Players(this.getMaxPlayers(), this.getPlayerCount()));
            final GameProfile[] arr5 = new GameProfile[Math.min(this.getPlayerCount(), 12)];
            final int integer6 = Mth.nextInt(this.random, 0, this.getPlayerCount() - arr5.length);
            for (int integer7 = 0; integer7 < arr5.length; ++integer7) {
                arr5[integer7] = this.playerList.getPlayers().get(integer6 + integer7).getGameProfile();
            }
            Collections.shuffle(Arrays.<GameProfile>asList(arr5));
            this.status.getPlayers().setSample(arr5);
        }
        if (this.tickCount % 6000 == 0) {
            MinecraftServer.LOGGER.debug("Autosave started");
            this.profiler.push("save");
            this.playerList.saveAll();
            this.saveAllChunks(true, false, false);
            this.profiler.pop();
            MinecraftServer.LOGGER.debug("Autosave finished");
        }
        this.profiler.push("snooper");
        if (!this.snooper.isStarted() && this.tickCount > 100) {
            this.snooper.start();
        }
        if (this.tickCount % 6000 == 0) {
            this.snooper.prepare();
        }
        this.profiler.pop();
        this.profiler.push("tallying");
        final long[] tickTimes = this.tickTimes;
        final int n = this.tickCount % 100;
        final long n2 = Util.getNanos() - long3;
        tickTimes[n] = n2;
        final long long4 = n2;
        this.averageTickTime = this.averageTickTime * 0.8f + long4 / 1000000.0f * 0.19999999f;
        final long long5 = Util.getNanos();
        this.frameTimer.logFrameDuration(long5 - long3);
        this.profiler.pop();
    }
    
    protected void tickChildren(final BooleanSupplier booleanSupplier) {
        this.profiler.push("commandFunctions");
        this.getFunctions().tick();
        this.profiler.popPush("levels");
        for (final ServerLevel zd4 : this.getAllLevels()) {
            final Level obj;
            this.profiler.push(() -> obj + " " + obj.dimension().location());
            if (this.tickCount % 20 == 0) {
                this.profiler.push("timeSync");
                this.playerList.broadcastAll(new ClientboundSetTimePacket(zd4.getGameTime(), zd4.getDayTime(), zd4.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)), zd4.dimension());
                this.profiler.pop();
            }
            this.profiler.push("tick");
            try {
                zd4.tick(booleanSupplier);
            }
            catch (Throwable throwable5) {
                final CrashReport j6 = CrashReport.forThrowable(throwable5, "Exception ticking world");
                zd4.fillReportDetails(j6);
                throw new ReportedException(j6);
            }
            this.profiler.pop();
            this.profiler.pop();
        }
        this.profiler.popPush("connection");
        this.getConnection().tick();
        this.profiler.popPush("players");
        this.playerList.tick();
        if (SharedConstants.IS_RUNNING_IN_IDE) {
            GameTestTicker.singleton.tick();
        }
        this.profiler.popPush("server gui refresh");
        for (int integer3 = 0; integer3 < this.tickables.size(); ++integer3) {
            this.tickables.get(integer3).run();
        }
        this.profiler.pop();
    }
    
    public boolean isNetherEnabled() {
        return true;
    }
    
    public void addTickable(final Runnable runnable) {
        this.tickables.add(runnable);
    }
    
    protected void setId(final String string) {
        this.serverId = string;
    }
    
    public boolean isShutdown() {
        return !this.serverThread.isAlive();
    }
    
    public File getFile(final String string) {
        return new File(this.getServerDirectory(), string);
    }
    
    public final ServerLevel overworld() {
        return this.levels.get(Level.OVERWORLD);
    }
    
    @Nullable
    public ServerLevel getLevel(final ResourceKey<Level> ug) {
        return this.levels.get(ug);
    }
    
    public Set<ResourceKey<Level>> levelKeys() {
        return this.levels.keySet();
    }
    
    public Iterable<ServerLevel> getAllLevels() {
        return this.levels.values();
    }
    
    public String getServerVersion() {
        return SharedConstants.getCurrentVersion().getName();
    }
    
    public int getPlayerCount() {
        return this.playerList.getPlayerCount();
    }
    
    public int getMaxPlayers() {
        return this.playerList.getMaxPlayers();
    }
    
    public String[] getPlayerNames() {
        return this.playerList.getPlayerNamesArray();
    }
    
    public String getServerModName() {
        return "vanilla";
    }
    
    public CrashReport fillReport(final CrashReport j) {
        if (this.playerList != null) {
            j.getSystemDetails().setDetail("Player Count", () -> this.playerList.getPlayerCount() + " / " + this.playerList.getMaxPlayers() + "; " + this.playerList.getPlayers());
        }
        final StringBuilder stringBuilder2;
        final Iterator<Pack> iterator;
        Pack aap4;
        j.getSystemDetails().setDetail("Data Packs", () -> {
            stringBuilder2 = new StringBuilder();
            this.packRepository.getSelectedPacks().iterator();
            while (iterator.hasNext()) {
                aap4 = iterator.next();
                if (stringBuilder2.length() > 0) {
                    stringBuilder2.append(", ");
                }
                stringBuilder2.append(aap4.getId());
                if (!aap4.getCompatibility().isCompatible()) {
                    stringBuilder2.append(" (incompatible)");
                }
            }
            return stringBuilder2.toString();
        });
        if (this.serverId != null) {
            j.getSystemDetails().setDetail("Server Id", () -> this.serverId);
        }
        return j;
    }
    
    public abstract Optional<String> getModdedStatus();
    
    @Override
    public void sendMessage(final Component mr, final UUID uUID) {
        MinecraftServer.LOGGER.info(mr.getString());
    }
    
    public KeyPair getKeyPair() {
        return this.keyPair;
    }
    
    public int getPort() {
        return this.port;
    }
    
    public void setPort(final int integer) {
        this.port = integer;
    }
    
    public String getSingleplayerName() {
        return this.singleplayerName;
    }
    
    public void setSingleplayerName(final String string) {
        this.singleplayerName = string;
    }
    
    public boolean isSingleplayer() {
        return this.singleplayerName != null;
    }
    
    public void setKeyPair(final KeyPair keyPair) {
        this.keyPair = keyPair;
    }
    
    public void setDifficulty(final Difficulty and, final boolean boolean2) {
        if (!boolean2 && this.worldData.isDifficultyLocked()) {
            return;
        }
        this.worldData.setDifficulty(this.worldData.isHardcore() ? Difficulty.HARD : and);
        this.updateMobSpawningFlags();
        this.getPlayerList().getPlayers().forEach(this::sendDifficultyUpdate);
    }
    
    public int getScaledTrackingDistance(final int integer) {
        return integer;
    }
    
    private void updateMobSpawningFlags() {
        for (final ServerLevel zd3 : this.getAllLevels()) {
            zd3.setSpawnSettings(this.isSpawningMonsters(), this.isSpawningAnimals());
        }
    }
    
    public void setDifficultyLocked(final boolean boolean1) {
        this.worldData.setDifficultyLocked(boolean1);
        this.getPlayerList().getPlayers().forEach(this::sendDifficultyUpdate);
    }
    
    private void sendDifficultyUpdate(final ServerPlayer ze) {
        final LevelData dab3 = ze.getLevel().getLevelData();
        ze.connection.send(new ClientboundChangeDifficultyPacket(dab3.getDifficulty(), dab3.isDifficultyLocked()));
    }
    
    protected boolean isSpawningMonsters() {
        return this.worldData.getDifficulty() != Difficulty.PEACEFUL;
    }
    
    public boolean isDemo() {
        return this.isDemo;
    }
    
    public void setDemo(final boolean boolean1) {
        this.isDemo = boolean1;
    }
    
    public String getResourcePack() {
        return this.resourcePack;
    }
    
    public String getResourcePackHash() {
        return this.resourcePackHash;
    }
    
    public void setResourcePack(final String string1, final String string2) {
        this.resourcePack = string1;
        this.resourcePackHash = string2;
    }
    
    @Override
    public void populateSnooper(final Snooper ano) {
        ano.setDynamicData("whitelist_enabled", false);
        ano.setDynamicData("whitelist_count", 0);
        if (this.playerList != null) {
            ano.setDynamicData("players_current", this.getPlayerCount());
            ano.setDynamicData("players_max", this.getMaxPlayers());
            ano.setDynamicData("players_seen", this.playerDataStorage.getSeenPlayers().length);
        }
        ano.setDynamicData("uses_auth", this.onlineMode);
        ano.setDynamicData("gui_state", this.hasGui() ? "enabled" : "disabled");
        ano.setDynamicData("run_time", (Util.getMillis() - ano.getStartupTime()) / 60L * 1000L);
        ano.setDynamicData("avg_tick_ms", (int)(Mth.average(this.tickTimes) * 1.0E-6));
        int integer3 = 0;
        for (final ServerLevel zd5 : this.getAllLevels()) {
            if (zd5 != null) {
                ano.setDynamicData("world[" + integer3 + "][dimension]", zd5.dimension().location());
                ano.setDynamicData("world[" + integer3 + "][mode]", this.worldData.getGameType());
                ano.setDynamicData("world[" + integer3 + "][difficulty]", zd5.getDifficulty());
                ano.setDynamicData("world[" + integer3 + "][hardcore]", this.worldData.isHardcore());
                ano.setDynamicData("world[" + integer3 + "][height]", this.maxBuildHeight);
                ano.setDynamicData("world[" + integer3 + "][chunks_loaded]", zd5.getChunkSource().getLoadedChunksCount());
                ++integer3;
            }
        }
        ano.setDynamicData("worlds", integer3);
    }
    
    public abstract boolean isDedicatedServer();
    
    public boolean usesAuthentication() {
        return this.onlineMode;
    }
    
    public void setUsesAuthentication(final boolean boolean1) {
        this.onlineMode = boolean1;
    }
    
    public boolean getPreventProxyConnections() {
        return this.preventProxyConnections;
    }
    
    public void setPreventProxyConnections(final boolean boolean1) {
        this.preventProxyConnections = boolean1;
    }
    
    public boolean isSpawningAnimals() {
        return true;
    }
    
    public boolean areNpcsEnabled() {
        return true;
    }
    
    public abstract boolean isEpollEnabled();
    
    public boolean isPvpAllowed() {
        return this.pvp;
    }
    
    public void setPvpAllowed(final boolean boolean1) {
        this.pvp = boolean1;
    }
    
    public boolean isFlightAllowed() {
        return this.allowFlight;
    }
    
    public void setFlightAllowed(final boolean boolean1) {
        this.allowFlight = boolean1;
    }
    
    public abstract boolean isCommandBlockEnabled();
    
    public String getMotd() {
        return this.motd;
    }
    
    public void setMotd(final String string) {
        this.motd = string;
    }
    
    public int getMaxBuildHeight() {
        return this.maxBuildHeight;
    }
    
    public void setMaxBuildHeight(final int integer) {
        this.maxBuildHeight = integer;
    }
    
    public boolean isStopped() {
        return this.stopped;
    }
    
    public PlayerList getPlayerList() {
        return this.playerList;
    }
    
    public void setPlayerList(final PlayerList abp) {
        this.playerList = abp;
    }
    
    public abstract boolean isPublished();
    
    public void setDefaultGameType(final GameType bpy) {
        this.worldData.setGameType(bpy);
    }
    
    @Nullable
    public ServerConnectionListener getConnection() {
        return this.connection;
    }
    
    public boolean isReady() {
        return this.isReady;
    }
    
    public boolean hasGui() {
        return false;
    }
    
    public abstract boolean publishServer(final GameType bpy, final boolean boolean2, final int integer);
    
    public int getTickCount() {
        return this.tickCount;
    }
    
    public Snooper getSnooper() {
        return this.snooper;
    }
    
    public int getSpawnProtectionRadius() {
        return 16;
    }
    
    public boolean isUnderSpawnProtection(final ServerLevel zd, final BlockPos fu, final Player bec) {
        return false;
    }
    
    public void setForceGameType(final boolean boolean1) {
        this.forceGameType = boolean1;
    }
    
    public boolean getForceGameType() {
        return this.forceGameType;
    }
    
    public boolean repliesToStatus() {
        return true;
    }
    
    public int getPlayerIdleTimeout() {
        return this.playerIdleTimeout;
    }
    
    public void setPlayerIdleTimeout(final int integer) {
        this.playerIdleTimeout = integer;
    }
    
    public MinecraftSessionService getSessionService() {
        return this.sessionService;
    }
    
    public GameProfileRepository getProfileRepository() {
        return this.profileRepository;
    }
    
    public GameProfileCache getProfileCache() {
        return this.profileCache;
    }
    
    public ServerStatus getStatus() {
        return this.status;
    }
    
    public void invalidateStatus() {
        this.lastServerStatus = 0L;
    }
    
    public int getAbsoluteMaxWorldSize() {
        return 29999984;
    }
    
    public boolean scheduleExecutables() {
        return super.scheduleExecutables() && !this.isStopped();
    }
    
    public Thread getRunningThread() {
        return this.serverThread;
    }
    
    public int getCompressionThreshold() {
        return 256;
    }
    
    public long getNextTickTime() {
        return this.nextTickTime;
    }
    
    public DataFixer getFixerUpper() {
        return this.fixerUpper;
    }
    
    public int getSpawnRadius(@Nullable final ServerLevel zd) {
        if (zd != null) {
            return zd.getGameRules().getInt(GameRules.RULE_SPAWN_RADIUS);
        }
        return 10;
    }
    
    public ServerAdvancementManager getAdvancements() {
        return this.resources.getAdvancements();
    }
    
    public ServerFunctionManager getFunctions() {
        return this.functionManager;
    }
    
    public CompletableFuture<Void> reloadResources(final Collection<String> collection) {
        final CompletableFuture<Void> completableFuture3 = CompletableFuture.<ImmutableList<? super Object>>supplyAsync(() -> collection.stream().map(this.packRepository::getPack).filter(Objects::nonNull).map(Pack::open).collect(ImmutableList.toImmutableList()), this).thenCompose(immutableList -> ServerResources.loadResources(immutableList, this.isDedicatedServer() ? Commands.CommandSelection.DEDICATED : Commands.CommandSelection.INTEGRATED, this.getFunctionCompilationLevel(), this.executor, this)).thenAcceptAsync(uw -> {
            this.resources.close();
            this.resources = uw;
            this.packRepository.setSelected(collection);
            this.worldData.setDataPackConfig(getSelectedPacks(this.packRepository));
            uw.updateGlobals();
            this.getPlayerList().saveAll();
            this.getPlayerList().reloadResources();
            this.functionManager.replaceLibrary(this.resources.getFunctionLibrary());
            this.structureManager.onResourceManagerReload(this.resources.getResourceManager());
            return;
        }, (Executor)this);
        if (this.isSameThread()) {
            this.managedBlock(completableFuture3::isDone);
        }
        return completableFuture3;
    }
    
    public static DataPackConfig configurePackRepository(final PackRepository<Pack> aar, final DataPackConfig bpn, final boolean boolean3) {
        aar.reload();
        if (boolean3) {
            aar.setSelected(Collections.<String>singleton("vanilla"));
            return new DataPackConfig(ImmutableList.<String>of("vanilla"), ImmutableList.of());
        }
        final Set<String> set4 = Sets.newLinkedHashSet();
        for (final String string6 : bpn.getEnabled()) {
            if (aar.isAvailable(string6)) {
                set4.add(string6);
            }
            else {
                MinecraftServer.LOGGER.warn("Missing data pack {}", string6);
            }
        }
        for (final Pack aap6 : aar.getAvailablePacks()) {
            final String string7 = aap6.getId();
            if (!bpn.getDisabled().contains(string7) && !set4.contains(string7)) {
                MinecraftServer.LOGGER.info("Found new data pack {}, loading it automatically", string7);
                set4.add(string7);
            }
        }
        if (set4.isEmpty()) {
            MinecraftServer.LOGGER.info("No datapacks selected, forcing vanilla");
            set4.add("vanilla");
        }
        aar.setSelected(set4);
        return getSelectedPacks(aar);
    }
    
    private static DataPackConfig getSelectedPacks(final PackRepository<?> aar) {
        final Collection<String> collection2 = aar.getSelectedIds();
        final List<String> list3 = ImmutableList.copyOf(collection2);
        final List<String> list4 = aar.getAvailableIds().stream().filter(string -> !collection2.contains(string)).collect(ImmutableList.toImmutableList());
        return new DataPackConfig(list3, list4);
    }
    
    public void kickUnlistedPlayers(final CommandSourceStack cz) {
        if (!this.isEnforceWhitelist()) {
            return;
        }
        final PlayerList abp3 = cz.getServer().getPlayerList();
        final UserWhiteList abw4 = abp3.getWhiteList();
        final List<ServerPlayer> list5 = Lists.newArrayList(abp3.getPlayers());
        for (final ServerPlayer ze7 : list5) {
            if (!abw4.isWhiteListed(ze7.getGameProfile())) {
                ze7.connection.disconnect(new TranslatableComponent("multiplayer.disconnect.not_whitelisted"));
            }
        }
    }
    
    public PackRepository<Pack> getPackRepository() {
        return this.packRepository;
    }
    
    public Commands getCommands() {
        return this.resources.getCommands();
    }
    
    public CommandSourceStack createCommandSourceStack() {
        final ServerLevel zd2 = this.overworld();
        return new CommandSourceStack(this, (zd2 == null) ? Vec3.ZERO : Vec3.atLowerCornerOf(zd2.getSharedSpawnPos()), Vec2.ZERO, zd2, 4, "Server", new TextComponent("Server"), this, null);
    }
    
    @Override
    public boolean acceptsSuccess() {
        return true;
    }
    
    @Override
    public boolean acceptsFailure() {
        return true;
    }
    
    public RecipeManager getRecipeManager() {
        return this.resources.getRecipeManager();
    }
    
    public TagManager getTags() {
        return this.resources.getTags();
    }
    
    public ServerScoreboard getScoreboard() {
        return this.scoreboard;
    }
    
    public CommandStorage getCommandStorage() {
        if (this.commandStorage == null) {
            throw new NullPointerException("Called before server init");
        }
        return this.commandStorage;
    }
    
    public LootTables getLootTables() {
        return this.resources.getLootTables();
    }
    
    public PredicateManager getPredicateManager() {
        return this.resources.getPredicateManager();
    }
    
    public GameRules getGameRules() {
        return this.overworld().getGameRules();
    }
    
    public CustomBossEvents getCustomBossEvents() {
        return this.customBossEvents;
    }
    
    public boolean isEnforceWhitelist() {
        return this.enforceWhitelist;
    }
    
    public void setEnforceWhitelist(final boolean boolean1) {
        this.enforceWhitelist = boolean1;
    }
    
    public float getAverageTickTime() {
        return this.averageTickTime;
    }
    
    public int getProfilePermissions(final GameProfile gameProfile) {
        if (!this.getPlayerList().isOp(gameProfile)) {
            return 0;
        }
        final ServerOpListEntry abr3 = this.getPlayerList().getOps().get(gameProfile);
        if (abr3 != null) {
            return abr3.getLevel();
        }
        if (this.isSingleplayerOwner(gameProfile)) {
            return 4;
        }
        if (this.isSingleplayer()) {
            return this.getPlayerList().isAllowCheatsForAllPlayers() ? 4 : 0;
        }
        return this.getOperatorUserPermissionLevel();
    }
    
    public FrameTimer getFrameTimer() {
        return this.frameTimer;
    }
    
    public ProfilerFiller getProfiler() {
        return this.profiler;
    }
    
    public abstract boolean isSingleplayerOwner(final GameProfile gameProfile);
    
    public void saveDebugReport(final Path path) throws IOException {
        final Path path2 = path.resolve("levels");
        for (final Map.Entry<ResourceKey<Level>, ServerLevel> entry5 : this.levels.entrySet()) {
            final ResourceLocation uh6 = entry5.getKey().location();
            final Path path3 = path2.resolve(uh6.getNamespace()).resolve(uh6.getPath());
            Files.createDirectories(path3, new FileAttribute[0]);
            entry5.getValue().saveDebugReport(path3);
        }
        this.dumpGameRules(path.resolve("gamerules.txt"));
        this.dumpClasspath(path.resolve("classpath.txt"));
        this.dumpCrashCategory(path.resolve("example_crash.txt"));
        this.dumpMiscStats(path.resolve("stats.txt"));
        this.dumpThreads(path.resolve("threads.txt"));
    }
    
    private void dumpMiscStats(final Path path) throws IOException {
        try (final Writer writer3 = Files.newBufferedWriter(path)) {
            writer3.write(String.format("pending_tasks: %d\n", this.getPendingTasksCount()));
            writer3.write(String.format("average_tick_time: %f\n", this.getAverageTickTime()));
            writer3.write(String.format("tick_times: %s\n", Arrays.toString(this.tickTimes)));
            writer3.write(String.format("queue: %s\n", Util.backgroundExecutor()));
        }
    }
    
    private void dumpCrashCategory(final Path path) throws IOException {
        final CrashReport j3 = new CrashReport("Server dump", new Exception("dummy"));
        this.fillReport(j3);
        try (final Writer writer4 = Files.newBufferedWriter(path)) {
            writer4.write(j3.getFriendlyReport());
        }
    }
    
    private void dumpGameRules(final Path path) throws IOException {
        try (final Writer writer3 = Files.newBufferedWriter(path)) {
            final List<String> list5 = Lists.newArrayList();
            final GameRules bpx6 = this.getGameRules();
            GameRules.visitGameRuleTypes(new GameRules.GameRuleTypeVisitor() {
                @Override
                public <T extends GameRules.Value<T>> void visit(final GameRules.Key<T> e, final GameRules.Type<T> f) {
                    list5.add(String.format("%s=%s\n", e.getId(), bpx6.<T>getRule(e).toString()));
                }
            });
            for (final String string8 : list5) {
                writer3.write(string8);
            }
        }
    }
    
    private void dumpClasspath(final Path path) throws IOException {
        try (final Writer writer3 = Files.newBufferedWriter(path)) {
            final String string5 = System.getProperty("java.class.path");
            final String string6 = System.getProperty("path.separator");
            for (final String string7 : Splitter.on(string6).split(string5)) {
                writer3.write(string7);
                writer3.write("\n");
            }
        }
    }
    
    private void dumpThreads(final Path path) throws IOException {
        final ThreadMXBean threadMXBean3 = ManagementFactory.getThreadMXBean();
        final ThreadInfo[] arr4 = threadMXBean3.dumpAllThreads(true, true);
        Arrays.<ThreadInfo>sort(arr4, Comparator.comparing(ThreadInfo::getThreadName));
        try (final Writer writer5 = Files.newBufferedWriter(path)) {
            for (final ThreadInfo threadInfo10 : arr4) {
                writer5.write(threadInfo10.toString());
                writer5.write(10);
            }
        }
    }
    
    private void startProfilerTick(@Nullable final SingleTickProfiler aml) {
        if (this.delayProfilerStart) {
            this.delayProfilerStart = false;
            this.continousProfiler.enable();
        }
        this.profiler = SingleTickProfiler.decorateFiller(this.continousProfiler.getFiller(), aml);
    }
    
    private void endProfilerTick(@Nullable final SingleTickProfiler aml) {
        if (aml != null) {
            aml.endTick();
        }
        this.profiler = this.continousProfiler.getFiller();
    }
    
    public boolean isProfiling() {
        return this.continousProfiler.isEnabled();
    }
    
    public void startProfiling() {
        this.delayProfilerStart = true;
    }
    
    public ProfileResults finishProfiling() {
        final ProfileResults amh2 = this.continousProfiler.getResults();
        this.continousProfiler.disable();
        return amh2;
    }
    
    public Path getWorldPath(final LevelResource dac) {
        return this.storageSource.getLevelPath(dac);
    }
    
    public boolean forceSynchronousWrites() {
        return true;
    }
    
    public StructureManager getStructureManager() {
        return this.structureManager;
    }
    
    public WorldData getWorldData() {
        return this.worldData;
    }
    
    static {
        LOGGER = LogManager.getLogger();
        USERID_CACHE_FILE = new File("usercache.json");
        DEMO_SETTINGS = new LevelSettings("Demo World", GameType.SURVIVAL, false, Difficulty.NORMAL, false, new GameRules(), DataPackConfig.DEFAULT);
    }
}
