cn.nukkit.Server.java Source code

Java tutorial

Introduction

Here is the source code for cn.nukkit.Server.java

Source

package cn.nukkit;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;

import org.apache.commons.collections.BidiMap;
import org.apache.commons.collections.bidimap.DualHashBidiMap;

import com.google.common.base.Preconditions;

import cn.nukkit.block.Block;
import cn.nukkit.blockentity.BlockEntity;
import cn.nukkit.blockentity.BlockEntityBeacon;
import cn.nukkit.blockentity.BlockEntityBrewingStand;
import cn.nukkit.blockentity.BlockEntityCauldron;
import cn.nukkit.blockentity.BlockEntityChest;
import cn.nukkit.blockentity.BlockEntityEnchantTable;
import cn.nukkit.blockentity.BlockEntityEnderChest;
import cn.nukkit.blockentity.BlockEntityFlowerPot;
import cn.nukkit.blockentity.BlockEntityFurnace;
import cn.nukkit.blockentity.BlockEntityItemFrame;
import cn.nukkit.blockentity.BlockEntitySign;
import cn.nukkit.blockentity.BlockEntitySkull;
import cn.nukkit.command.Command;
import cn.nukkit.command.CommandReader;
import cn.nukkit.command.CommandSender;
import cn.nukkit.command.ConsoleCommandSender;
import cn.nukkit.command.PluginIdentifiableCommand;
import cn.nukkit.command.SimpleCommandMap;
import cn.nukkit.entity.Attribute;
import cn.nukkit.entity.Entity;
import cn.nukkit.entity.EntityHuman;
import cn.nukkit.entity.boss.EntityElderGuardian;
import cn.nukkit.entity.boss.EntityEnderDragon;
import cn.nukkit.entity.boss.EntityWither;
import cn.nukkit.entity.data.Skin;
import cn.nukkit.entity.item.EntityBoat;
import cn.nukkit.entity.item.EntityEnderCrystal;
import cn.nukkit.entity.item.EntityExpBottle;
import cn.nukkit.entity.item.EntityFallingBlock;
import cn.nukkit.entity.item.EntityItem;
import cn.nukkit.entity.item.EntityMinecartChest;
import cn.nukkit.entity.item.EntityMinecartEmpty;
import cn.nukkit.entity.item.EntityMinecartHopper;
import cn.nukkit.entity.item.EntityMinecartTNT;
import cn.nukkit.entity.item.EntityPainting;
import cn.nukkit.entity.item.EntityPotion;
import cn.nukkit.entity.item.EntityPrimedTNT;
import cn.nukkit.entity.item.EntityXPOrb;
import cn.nukkit.entity.mob.EntityBlaze;
import cn.nukkit.entity.mob.EntityCaveSpider;
import cn.nukkit.entity.mob.EntityCreeper;
import cn.nukkit.entity.mob.EntityEnderman;
import cn.nukkit.entity.mob.EntityEndermite;
import cn.nukkit.entity.mob.EntityGhast;
import cn.nukkit.entity.mob.EntityGuardian;
import cn.nukkit.entity.mob.EntityHask;
import cn.nukkit.entity.mob.EntityMagmaCube;
import cn.nukkit.entity.mob.EntityShulker;
import cn.nukkit.entity.mob.EntitySilverfish;
import cn.nukkit.entity.mob.EntitySkeleton;
import cn.nukkit.entity.mob.EntitySlime;
import cn.nukkit.entity.mob.EntitySpider;
import cn.nukkit.entity.mob.EntityStray;
import cn.nukkit.entity.mob.EntityWitch;
import cn.nukkit.entity.mob.EntityWitherSkeleton;
import cn.nukkit.entity.mob.EntityZombie;
import cn.nukkit.entity.mob.EntityZombiePigman;
import cn.nukkit.entity.mob.EntityZombieVillager;
import cn.nukkit.entity.passive.EntityBat;
import cn.nukkit.entity.passive.EntityChicken;
import cn.nukkit.entity.passive.EntityCow;
import cn.nukkit.entity.passive.EntityDonkey;
import cn.nukkit.entity.passive.EntityHorse;
import cn.nukkit.entity.passive.EntityMooshroom;
import cn.nukkit.entity.passive.EntityMule;
import cn.nukkit.entity.passive.EntityOcelot;
import cn.nukkit.entity.passive.EntityPig;
import cn.nukkit.entity.passive.EntityPolarBear;
import cn.nukkit.entity.passive.EntityRabbit;
import cn.nukkit.entity.passive.EntitySheep;
import cn.nukkit.entity.passive.EntitySkeletonHorse;
import cn.nukkit.entity.passive.EntitySquid;
import cn.nukkit.entity.passive.EntityVillager;
import cn.nukkit.entity.passive.EntityWolf;
import cn.nukkit.entity.passive.EntityZombieHorse;
import cn.nukkit.entity.projectile.EntityArrow;
import cn.nukkit.entity.projectile.EntityEnderPearl;
import cn.nukkit.entity.projectile.EntityFishingHook;
import cn.nukkit.entity.projectile.EntitySnowball;
import cn.nukkit.entity.weather.EntityLightning;
import cn.nukkit.event.HandlerList;
import cn.nukkit.event.level.LevelInitEvent;
import cn.nukkit.event.level.LevelLoadEvent;
import cn.nukkit.event.server.QueryRegenerateEvent;
import cn.nukkit.inventory.CraftingManager;
import cn.nukkit.inventory.FurnaceRecipe;
import cn.nukkit.inventory.Recipe;
import cn.nukkit.inventory.ShapedRecipe;
import cn.nukkit.inventory.ShapelessRecipe;
import cn.nukkit.item.Item;
import cn.nukkit.item.enchantment.Enchantment;
import cn.nukkit.lang.BaseLang;
import cn.nukkit.lang.TextContainer;
import cn.nukkit.lang.TranslationContainer;
import cn.nukkit.level.Level;
import cn.nukkit.level.Position;
import cn.nukkit.level.format.LevelProvider;
import cn.nukkit.level.format.LevelProviderManager;
import cn.nukkit.level.format.anvil.Anvil;
import cn.nukkit.level.format.leveldb.LevelDB;
import cn.nukkit.level.format.mcregion.McRegion;
import cn.nukkit.level.generator.Flat;
import cn.nukkit.level.generator.Generator;
import cn.nukkit.level.generator.Nether;
import cn.nukkit.level.generator.Normal;
import cn.nukkit.level.generator.biome.Biome;
import cn.nukkit.math.NukkitMath;
import cn.nukkit.metadata.EntityMetadataStore;
import cn.nukkit.metadata.LevelMetadataStore;
import cn.nukkit.metadata.PlayerMetadataStore;
import cn.nukkit.nbt.NBTIO;
import cn.nukkit.nbt.tag.CompoundTag;
import cn.nukkit.nbt.tag.DoubleTag;
import cn.nukkit.nbt.tag.FloatTag;
import cn.nukkit.nbt.tag.ListTag;
import cn.nukkit.network.CompressBatchedTask;
import cn.nukkit.network.Network;
import cn.nukkit.network.RakNetInterface;
import cn.nukkit.network.SourceInterface;
import cn.nukkit.network.protocol.BatchPacket;
import cn.nukkit.network.protocol.CraftingDataPacket;
import cn.nukkit.network.protocol.DataPacket;
import cn.nukkit.network.protocol.PlayerListPacket;
import cn.nukkit.network.protocol.ProtocolInfo;
import cn.nukkit.network.query.QueryHandler;
import cn.nukkit.network.rcon.RCON;
import cn.nukkit.permission.BanEntry;
import cn.nukkit.permission.BanList;
import cn.nukkit.permission.DefaultPermissions;
import cn.nukkit.permission.Permissible;
import cn.nukkit.plugin.JavaPluginLoader;
import cn.nukkit.plugin.Plugin;
import cn.nukkit.plugin.PluginCompiler;
import cn.nukkit.plugin.PluginLoadOrder;
import cn.nukkit.plugin.PluginManager;
import cn.nukkit.plugin.service.NKServiceManager;
import cn.nukkit.plugin.service.ServiceManager;
import cn.nukkit.potion.Effect;
import cn.nukkit.potion.Potion;
import cn.nukkit.resourcepacks.ResourcePackManager;
import cn.nukkit.scheduler.FileWriteTask;
import cn.nukkit.scheduler.ServerScheduler;
import cn.nukkit.utils.Binary;
import cn.nukkit.utils.Config;
import cn.nukkit.utils.ConfigSection;
import cn.nukkit.utils.LevelException;
import cn.nukkit.utils.MainLogger;
import cn.nukkit.utils.ServerException;
import cn.nukkit.utils.ServerKiller;
import cn.nukkit.utils.TextFormat;
import cn.nukkit.utils.Utils;
import cn.nukkit.utils.Zlib;
import co.aikar.timings.Timings;

/**
 * @author MagicDroidX
 * @author Box
 */
public class Server {

    public static final String BROADCAST_CHANNEL_ADMINISTRATIVE = "nukkit.broadcast.admin";
    public static final String BROADCAST_CHANNEL_USERS = "nukkit.broadcast.user";

    private static Server instance = null;

    private BanList banByName = null;

    private BanList banByIP = null;

    private Config operators = null;

    private Config whitelist = null;

    private boolean isRunning = true;

    private boolean hasStopped = false;

    private PluginManager pluginManager = null;

    private int profilingTickrate = 20;

    private ServerScheduler scheduler = null;

    private int tickCounter;

    private long nextTick;

    private final float[] tickAverage = { 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
            20, 20 };

    private final float[] useAverage = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

    private float maxTick = 20;

    private float maxUse = 0;

    private int sendUsageTicker = 0;

    private boolean dispatchSignals = false;

    private final MainLogger logger;

    private final CommandReader console;

    private SimpleCommandMap commandMap;

    private CraftingManager craftingManager;

    private ResourcePackManager resourcePackManager;

    private ConsoleCommandSender consoleSender;

    private int maxPlayers;

    private boolean autoSave;

    private RCON rcon;

    private EntityMetadataStore entityMetadata;

    private PlayerMetadataStore playerMetadata;

    private LevelMetadataStore levelMetadata;

    private Network network;

    private boolean networkCompressionAsync = true;
    public int networkCompressionLevel = 7;

    private boolean autoTickRate = true;
    private int autoTickRateLimit = 20;
    private boolean alwaysTickPlayers = false;
    private int baseTickRate = 1;
    private Boolean getAllowFlight = null;

    private int autoSaveTicker = 0;
    private int autoSaveTicks = 6000;

    private BaseLang baseLang;

    private boolean forceLanguage = false;

    private UUID serverID;

    private final String filePath;
    private final String dataPath;
    private final String pluginPath;
    private String defaultplugin = null;

    private final Set<UUID> uniquePlayers = new HashSet<>();

    private QueryHandler queryHandler;

    private QueryRegenerateEvent queryRegenerateEvent;

    private Config properties;
    private Config config;

    private final Map<String, Player> players = new HashMap<>();

    private final Map<UUID, Player> playerList = new HashMap<>();

    private final Map<Integer, String> identifier = new HashMap<>();

    private final Map<Integer, Level> levels = new HashMap<>();

    private final ServiceManager serviceManager = new NKServiceManager();

    private Level defaultLevel = null;

    private Thread currentThread;
    private Map<String, Object> jupiterconfig;

    @SuppressWarnings("unchecked")
    Server(MainLogger logger, final String filePath, String dataPath, String pluginPath) {
        Preconditions.checkState(instance == null, "Already initialized!");
        currentThread = Thread.currentThread(); // Saves the current thread instance as a reference, used in Server#isPrimaryThread()
        instance = this;
        this.logger = logger;

        this.filePath = filePath;
        if (!new File(dataPath + "worlds/").exists()) {
            new File(dataPath + "worlds/").mkdirs();
            this.getLogger().info(TextFormat.AQUA + dataPath + "worlds/  was created!");
        }

        if (!new File(dataPath + "players/").exists()) {
            new File(dataPath + "players/").mkdirs();
            this.getLogger().info(TextFormat.AQUA + dataPath + "players/  was created!");
        }

        if (!new File(pluginPath).exists()) {
            new File(pluginPath).mkdirs();
            this.getLogger().info(TextFormat.AQUA + pluginPath + "plugins/  was created!");
            this.getLogger().info(TextFormat.AQUA + dataPath + "worlds/  ?????");
        }

        if (!new File(dataPath + "players/").exists()) {
            new File(dataPath + "players/").mkdirs();
            this.getLogger().info(TextFormat.AQUA + dataPath + "players/  ?????");
        }

        if (!new File(pluginPath).exists()) {
            new File(pluginPath).mkdirs();
            this.getLogger().info(TextFormat.AQUA + pluginPath + "  ?????");
        }

        if (!new File(dataPath + "unpackedPlugins/").exists()) {
            new File(dataPath + "unpackedPlugins/").mkdirs();
            this.getLogger().info(TextFormat.AQUA + pluginPath + "unpackedPlugins/  ?????");
        }

        if (!new File(dataPath + "compileOrder/").exists()) {
            new File(dataPath + "compileOrder/").mkdirs();
            this.getLogger().info(TextFormat.AQUA + pluginPath + "compileOrder/  ?????");
        }

        this.dataPath = new File(dataPath).getAbsolutePath() + "/";

        this.pluginPath = new File(pluginPath).getAbsolutePath() + "/";

        this.console = new CommandReader();
        //todo: VersionString ??

        if (!new File(this.dataPath + "nukkit.yml").exists()) {
            this.getLogger().info(TextFormat.GREEN + "Welcome! Please choose a language first!");
            try {
                String[] lines = Utils
                        .readFile(this.getClass().getClassLoader().getResourceAsStream("lang/language.list"))
                        .split("\n");
                for (String line : lines) {
                    this.getLogger().info(line);
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            String fallback = BaseLang.FALLBACK_LANGUAGE;
            String language = null;
            while (language == null) {
                String lang = this.console.readLine();
                InputStream conf = this.getClass().getClassLoader()
                        .getResourceAsStream("lang/" + lang + "/lang.ini");
                if (conf != null) {
                    language = lang;
                }
            }

            InputStream advacedConf = this.getClass().getClassLoader()
                    .getResourceAsStream("lang/" + language + "/nukkit.yml");
            if (advacedConf == null) {
                advacedConf = this.getClass().getClassLoader()
                        .getResourceAsStream("lang/" + fallback + "/nukkit.yml");
            }

            try {
                Utils.writeFile(this.dataPath + "nukkit.yml", advacedConf);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

        }

        this.console.start();
        this.logger.info(TextFormat.GREEN + "nukkit.yml" + TextFormat.WHITE + "?????...");
        this.config = new Config(this.dataPath + "nukkit.yml", Config.YAML);

        this.logger
                .info(TextFormat.GREEN + "server.properties" + TextFormat.WHITE + "?????...");
        this.properties = new Config(this.dataPath + "server.properties", Config.PROPERTIES, new ConfigSection() {
            {
                put("motd", "Jupiter Server For Minecraft: PE");
                put("server-port", 19132);
                put("server-ip", "0.0.0.0");
                put("view-distance", 10);
                put("white-list", false);
                put("achievements", true);
                put("announce-player-achievements", true);
                put("spawn-protection", 16);
                put("max-players", 20);
                put("allow-flight", false);
                put("spawn-animals", true);
                put("spawn-mobs", true);
                put("gamemode", 0);
                put("force-gamemode", false);
                put("hardcore", false);
                put("pvp", true);
                put("difficulty", 1);
                put("generator-settings", "");
                put("level-name", "world");
                put("level-seed", "");
                put("level-type", "DEFAULT");
                put("enable-query", true);
                put("enable-rcon", false);
                put("rcon.password", Base64.getEncoder()
                        .encodeToString(UUID.randomUUID().toString().replace("-", "").getBytes()).substring(3, 13));
                put("auto-save", true);
                put("force-resources", false);
            }
        });

        this.logger.info(TextFormat.GREEN + "jupiter.yml" + TextFormat.WHITE + "?????...");

        if (!new File(this.dataPath + "jupiter.yml").exists()) {
            InputStream advacedConf = this.getClass().getClassLoader().getResourceAsStream("lang/jpn/jupiter.yml");
            if (advacedConf == null)
                this.getLogger().error(
                        "Jupiter.yml????????????????");

            try {
                Utils.writeFile(this.dataPath + "jupiter.yml", advacedConf);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        this.loadJupiterConfig();

        InputStream advacedConf1 = this.getClass().getClassLoader().getResourceAsStream("lang/jpn/jupiter.yml");
        if (advacedConf1 == null)
            this.getLogger().error(
                    "Jupiter.yml????????????????");

        try {
            Utils.writeFile(this.dataPath + "jupiter1.yml", advacedConf1);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        //get Jupiter.yml in the jar
        Config jupiter = new Config(this.getDataPath() + "jupiter1.yml");

        Map<String, Object> jmap = jupiter.getAll();

        Collection<Object> objj = jmap.values();
        Object[] objj1 = objj.toArray();
        Object objj2 = objj1[objj1.length - 1];

        BidiMap mapp = new DualHashBidiMap(jmap);
        String keyy = (String) mapp.getKey(objj2);

        //get JupiterConfig key in the delectory
        Collection<Object> obj = jupiterconfig.values();
        Object[] obj1 = obj.toArray();
        Object obj2 = obj1[obj1.length - 1];

        BidiMap map = new DualHashBidiMap(jupiterconfig);
        String key1 = (String) map.getKey(obj2);

        //delete jupiter1.yml
        File jf = new File(this.dataPath + "jupiter1.yml");
        jf.delete();

        if (!keyy.equals(key1)) {

            File conf = new File(this.dataPath + "jupiter.yml");
            conf.delete();

            InputStream advacedConf = this.getClass().getClassLoader().getResourceAsStream("lang/jpn/jupiter.yml");
            if (advacedConf == null)
                this.getLogger().error(
                        "Jupiter.yml????????????????");

            try {
                Utils.writeFile(this.dataPath + "jupiter.yml", advacedConf);
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }

            this.getLogger()
                    .info(TextFormat.AQUA + "Jupiter.yml????????????");

            this.loadJupiterConfig();

        }

        if (this.getJupiterConfigBoolean("destroy-block-particle")) {
            Level.sendDestroyParticle = true;
        } else {
            Level.sendDestroyParticle = false;
        }

        this.forceLanguage = (Boolean) this.getConfig("settings.force-language", false);
        this.baseLang = new BaseLang((String) this.getConfig("settings.language", BaseLang.FALLBACK_LANGUAGE));
        this.logger.info(this.getLanguage().translateString("language.selected",
                new String[] { getLanguage().getName(), getLanguage().getLang() }));
        this.logger.info(this.getLanguage().translateString("nukkit.server.start",
                TextFormat.AQUA + this.getVersion() + TextFormat.WHITE));

        Object poolSize = this.getConfig("settings.async-workers", "auto");
        if (!(poolSize instanceof Integer)) {
            try {
                poolSize = Integer.valueOf((String) poolSize);
            } catch (Exception e) {
                poolSize = Math.max(Runtime.getRuntime().availableProcessors() + 1, 4);
            }
        }

        ServerScheduler.WORKERS = (int) poolSize;

        int threshold;
        try {
            threshold = Integer.valueOf(String.valueOf(this.getConfig("network.batch-threshold", 256)));
        } catch (Exception e) {
            threshold = 256;
        }

        if (threshold < 0) {
            threshold = -1;
        }

        Network.BATCH_THRESHOLD = threshold;
        this.networkCompressionLevel = (int) this.getConfig("network.compression-level", 7);
        this.networkCompressionAsync = (boolean) this.getConfig("network.async-compression", true);

        this.networkCompressionLevel = (int) this.getConfig("network.compression-level", 7);
        this.networkCompressionAsync = (boolean) this.getConfig("network.async-compression", true);

        this.autoTickRate = (boolean) this.getConfig("level-settings.auto-tick-rate", true);
        this.autoTickRateLimit = (int) this.getConfig("level-settings.auto-tick-rate-limit", 20);
        this.alwaysTickPlayers = (boolean) this.getConfig("level-settings.always-tick-players", false);
        this.baseTickRate = (int) this.getConfig("level-settings.base-tick-rate", 1);

        this.scheduler = new ServerScheduler();

        if (this.getPropertyBoolean("enable-rcon", false)) {
            this.rcon = new RCON(this, this.getPropertyString("rcon.password", ""),
                    (!this.getIp().equals("")) ? this.getIp() : "0.0.0.0",
                    this.getPropertyInt("rcon.port", this.getPort()));
        }

        this.entityMetadata = new EntityMetadataStore();
        this.playerMetadata = new PlayerMetadataStore();
        this.levelMetadata = new LevelMetadataStore();

        this.operators = new Config(this.dataPath + "ops.txt", Config.ENUM);
        this.whitelist = new Config(this.dataPath + "white-list.txt", Config.ENUM);
        this.banByName = new BanList(this.dataPath + "banned-players.json");
        this.banByName.load();
        this.banByIP = new BanList(this.dataPath + "banned-ips.json");
        this.banByIP.load();

        this.maxPlayers = this.getPropertyInt("max-players", 20);
        this.setAutoSave(this.getPropertyBoolean("auto-save", true));

        if (this.getPropertyBoolean("hardcore", false) && this.getDifficulty() < 3) {
            this.setPropertyInt("difficulty", 3);
        }

        Nukkit.DEBUG = (int) this.getConfig("debug.level", 1);
        if (this.logger instanceof MainLogger) {
            this.logger.setLogDebug(Nukkit.DEBUG > 1);
        }

        this.logger.info(this.getLanguage().translateString("nukkit.server.networkStart",
                new String[] { this.getIp().equals("") ? "*" : this.getIp(), String.valueOf(this.getPort()) }));
        this.serverID = UUID.randomUUID();

        this.network = new Network(this);
        this.network.setName(this.getMotd());

        this.logger.info(this.getLanguage().translateString("nukkit.server.info", this.getName(),
                TextFormat.YELLOW + this.getNukkitVersion() + TextFormat.WHITE,
                TextFormat.AQUA + this.getCodename() + TextFormat.WHITE, this.getApiVersion()));
        this.logger.info(this.getLanguage().translateString("nukkit.server.license", this.getName()));

        this.consoleSender = new ConsoleCommandSender();
        this.commandMap = new SimpleCommandMap(this);

        this.registerEntities();
        this.registerBlockEntities();

        Block.init();
        Enchantment.init();
        Item.init();
        Biome.init();
        Effect.init();
        Potion.init();
        Attribute.init();

        this.craftingManager = new CraftingManager();
        this.resourcePackManager = new ResourcePackManager(new File(Nukkit.DATA_PATH, "resource_packs"));

        this.pluginManager = new PluginManager(this, this.commandMap);
        this.pluginManager.subscribeToPermission(Server.BROADCAST_CHANNEL_ADMINISTRATIVE, this.consoleSender);

        this.pluginManager.registerInterface(JavaPluginLoader.class);

        this.queryRegenerateEvent = new QueryRegenerateEvent(this, 5);

        this.network.registerInterface(new RakNetInterface(this));

        Calendar now = Calendar.getInstance();

        int y = now.get(Calendar.YEAR);
        int mo = now.get(Calendar.MONTH) + 1;
        int d = now.get(Calendar.DATE);
        int h = now.get(Calendar.HOUR_OF_DAY);
        int m = now.get(Calendar.MINUTE);
        int s = now.get(Calendar.SECOND);

        this.logger.info(TextFormat.AQUA
                + "==================================================================================");
        this.logger.info(TextFormat.AQUA
                + "");
        this.logger.info(
                "   ||  || ||  ||  ||  ||  || ||");
        this.logger.info("   |  |  |  | |  |  | || |  |  |  |  |  | |  | || |");
        this.logger.info("   |  |  |  | |  |  |  |  |  |      |  |      |  | |  | ");
        this.logger.info("  _|  |  |  | |  |  | |   |  |      |  |      | |  | | ");
        this.logger.info(" |____|  |_____|  |_|         |__|      |__|      |  | |_|    _|");
        this.logger.info("");
        this.logger.info(TextFormat.AQUA
                + "");

        this.logger.info(TextFormat.AQUA
                + "----------------------------------------------------------------------------------");
        this.logger.info(TextFormat.GREEN + "Jupiter - Nukkit Fork");
        this.logger.info(TextFormat.YELLOW + "JupiterDevelopmentTeam ");
        this.logger.info(TextFormat.AQUA
                + "----------------------------------------------------------------------------------");
        this.logger.info(
                ": " + TextFormat.BLUE + y + "/" + mo + "/" + d + " " + h + "" + m + "" + s + "");
        this.logger.info("???: " + TextFormat.GREEN + this.getMotd());
        this.logger.info("ip: " + TextFormat.GREEN + this.getIp());
        this.logger.info("?: " + TextFormat.GREEN + this.getPort());
        this.logger.info("Nukkit?: " + TextFormat.LIGHT_PURPLE + this.getNukkitVersion());
        this.logger.info("API?: " + TextFormat.LIGHT_PURPLE + this.getApiVersion());
        this.logger.info("?: " + TextFormat.LIGHT_PURPLE + this.getCodename());
        this.logger.info(TextFormat.AQUA
                + "==================================================================================");

        if (this.getJupiterConfigBoolean("jupiter-compiler-mode")) {
            this.logger.info(TextFormat.YELLOW
                    + "----------------------------------------------------------------------------------");
            getLogger().info(TextFormat.AQUA + "?????...");
            File f = new File(dataPath + "compileOrder/");
            File[] list = f.listFiles();
            for (int i = 0; i < list.length; i++) {
                if (new PluginCompiler().Compile(list[i]))
                    getLogger().info(list[i].toPath().toString() + " :" + TextFormat.GREEN + "");
                else
                    getLogger().info(list[i].toPath().toString() + " :" + TextFormat.RED + "");
            }
            this.logger.info(TextFormat.YELLOW
                    + "----------------------------------------------------------------------------------");
        }

        this.logger.info(TextFormat.LIGHT_PURPLE
                + "----------------------------------------------------------------------------------");
        getLogger().info(TextFormat.AQUA + "?????...");
        this.pluginManager.loadPlugins(this.pluginPath);

        this.enablePlugins(PluginLoadOrder.STARTUP);

        LevelProviderManager.addProvider(this, Anvil.class);
        LevelProviderManager.addProvider(this, McRegion.class);
        LevelProviderManager.addProvider(this, LevelDB.class);

        Generator.addGenerator(Flat.class, "flat", Generator.TYPE_FLAT);
        Generator.addGenerator(Normal.class, "normal", Generator.TYPE_INFINITE);
        Generator.addGenerator(Normal.class, "default", Generator.TYPE_INFINITE);
        Generator.addGenerator(Nether.class, "nether", Generator.TYPE_NETHER);
        //todo: add old generator and hell generator

        for (String name : ((Map<String, Object>) this.getConfig("worlds", new HashMap<>())).keySet()) {
            if (!this.loadLevel(name)) {
                long seed;
                try {
                    seed = ((Integer) this.getConfig("worlds." + name + ".seed")).longValue();
                } catch (Exception e) {
                    seed = System.currentTimeMillis();
                }

                Map<String, Object> options = new HashMap<>();
                String[] opts = ((String) this.getConfig("worlds." + name + ".generator",
                        Generator.getGenerator("default").getSimpleName())).split(":");
                Class<? extends Generator> generator = Generator.getGenerator(opts[0]);
                if (opts.length > 1) {
                    String preset = "";
                    for (int i = 1; i < opts.length; i++) {
                        preset += opts[i] + ":";
                    }
                    preset = preset.substring(0, preset.length() - 1);

                    options.put("preset", preset);
                }

                this.generateLevel(name, seed, generator, options);
            }
        }

        if (this.getDefaultLevel() == null) {
            String defaultName = this.getPropertyString("level-name", "world");
            if (defaultName == null || "".equals(defaultName.trim())) {
                this.getLogger().warning("level-name cannot be null, using default");
                defaultName = "world";
                this.setPropertyString("level-name", defaultName);
            }

            if (!this.loadLevel(defaultName)) {
                long seed;
                String seedString = String.valueOf(this.getProperty("level-seed", System.currentTimeMillis()));
                try {
                    seed = Long.valueOf(seedString);
                } catch (NumberFormatException e) {
                    seed = seedString.hashCode();
                }
                this.generateLevel(defaultName, seed == 0 ? System.currentTimeMillis() : seed);
            }

            this.setDefaultLevel(this.getLevelByName(defaultName));
        }

        this.properties.save(true);

        if (this.getDefaultLevel() == null) {
            this.getLogger().emergency(this.getLanguage().translateString("nukkit.level.defaultError"));
            this.forceShutdown();

            return;
        }

        if ((int) this.getConfig("ticks-per.autosave", 6000) > 0) {
            this.autoSaveTicks = (int) this.getConfig("ticks-per.autosave", 6000);
        }

        this.enablePlugins(PluginLoadOrder.POSTWORLD);
        this.logger.info(TextFormat.LIGHT_PURPLE
                + "----------------------------------------------------------------------------------");

        this.start();
    }

    /**
     * ???????
     * <br>?????
     * (...isMuted()?)
     * @see "?????"
     * @see Player#sendImportantMessage(String) sendImportantMessage
     * @see Player#isMuted() isMuted()
     * @param message ?
     * @return int
     */
    public int broadcastMessage(String message) {
        return this.broadcast(message, BROADCAST_CHANNEL_USERS);
    }

    /**
     * ???????
     * <br>?????
     * (...isMuted()?)
     * @see "?????"
     * @see Player#sendImportantMessage(String) sendImportantMessage
     * @see Player#isMuted() isMuted()
     * @param message ?
     * @return int
     */
    public int broadcastMessage(TextContainer message) {
        return this.broadcast(message, BROADCAST_CHANNEL_USERS);
    }

    public int broadcastMessage(String message, CommandSender[] recipients) {
        for (CommandSender recipient : recipients) {
            recipient.sendMessage(message);
        }

        return recipients.length;
    }

    public int broadcastMessage(String message, Collection<CommandSender> recipients) {
        for (CommandSender recipient : recipients) {
            recipient.sendMessage(message);
        }

        return recipients.size();
    }

    public int broadcastMessage(TextContainer message, Collection<CommandSender> recipients) {
        for (CommandSender recipient : recipients) {
            recipient.sendMessage(message);
        }

        return recipients.size();
    }

    /**
     * ????????
     * <br>?????
     * (...isMuted()?)
     * @param message ?
     * @return int
     */
    public int broadcastPopup(String message) {
        return this.broadcastPopup(message, BROADCAST_CHANNEL_USERS);
    }

    /**
     * ????????
     * <br>?????
     * @param message ?
     * @return int
     */
    public int broadcastTip(String message) {
        return this.broadcastPopup(message, BROADCAST_CHANNEL_USERS);
    }

    /**
     * ???????
     * <br>?????
     * @param message ?
     * @return int
     */
    public int broadcastTitle(String message) {
        return this.broadcastTitle(message, BROADCAST_CHANNEL_USERS);
    }

    /**
     * ???????
     * <br>?????
     * @param message ?
     * @return int
     */
    public int broadcastSubtitle(String message) {
        return this.broadcastSubtitle(message, BROADCAST_CHANNEL_USERS);
    }

    /**
     * ???????
     * <br>????
     * (...isMuted()?)
     * @see Player#isMuted() isMuted()
     * @param message ?
     * @return int
     * @author Itsu
     */
    public int broadcastImportantMessage(String message) {
        return this.broadcastImportantMessage(message, BROADCAST_CHANNEL_USERS);
    }

    public int broadcast(String message, String permissions) {
        Set<CommandSender> recipients = new HashSet<>();

        for (String permission : permissions.split(";")) {
            for (Permissible permissible : this.pluginManager.getPermissionSubscriptions(permission)) {
                if (permissible instanceof CommandSender && permissible.hasPermission(permission)) {
                    recipients.add((CommandSender) permissible);
                }
            }
        }

        for (CommandSender recipient : recipients) {
            recipient.sendMessage(message);
        }

        return recipients.size();
    }

    public int broadcast(TextContainer message, String permissions) {
        Set<CommandSender> recipients = new HashSet<>();

        for (String permission : permissions.split(";")) {
            for (Permissible permissible : this.pluginManager.getPermissionSubscriptions(permission)) {
                if (permissible instanceof CommandSender && permissible.hasPermission(permission)) {
                    recipients.add((CommandSender) permissible);
                }
            }
        }

        for (CommandSender recipient : recipients) {
            recipient.sendMessage(message);
        }

        return recipients.size();
    }

    public int broadcastPopup(String message, String permissions) {
        Set<Player> recipients = new HashSet<>();

        for (String permission : permissions.split(";")) {
            for (Permissible permissible : this.pluginManager.getPermissionSubscriptions(permission)) {
                if (permissible instanceof Player && permissible.hasPermission(permission)) {
                    recipients.add((Player) permissible);
                }
            }
        }

        for (Player recipient : recipients) {
            recipient.sendPopup(message);
        }

        return recipients.size();
    }

    public int broadcastTip(String message, String permissions) {
        Set<Player> recipients = new HashSet<>();

        for (String permission : permissions.split(";")) {
            for (Permissible permissible : this.pluginManager.getPermissionSubscriptions(permission)) {
                if (permissible instanceof Player && permissible.hasPermission(permission)) {
                    recipients.add((Player) permissible);
                }
            }
        }

        for (Player recipient : recipients) {
            recipient.sendTip(message);
        }

        return recipients.size();
    }

    public int broadcastTitle(String message, String permissions) {
        Set<Player> recipients = new HashSet<>();

        for (String permission : permissions.split(";")) {
            for (Permissible permissible : this.pluginManager.getPermissionSubscriptions(permission)) {
                if (permissible instanceof Player && permissible.hasPermission(permission)) {
                    recipients.add((Player) permissible);
                }
            }
        }

        for (Player recipient : recipients) {
            recipient.sendTitle(message);
        }

        return recipients.size();
    }

    public int broadcastSubtitle(String message, String permissions) {
        Set<Player> recipients = new HashSet<>();

        for (String permission : permissions.split(";")) {
            for (Permissible permissible : this.pluginManager.getPermissionSubscriptions(permission)) {
                if (permissible instanceof Player && permissible.hasPermission(permission)) {
                    recipients.add((Player) permissible);
                }
            }
        }

        for (Player recipient : recipients) {
            recipient.setSubtitle(message);
        }

        return recipients.size();
    }

    public int broadcastImportantMessage(String message, String permissions) {
        Set<CommandSender> recipients = new HashSet<>();

        for (String permission : permissions.split(";")) {
            for (Permissible permissible : this.pluginManager.getPermissionSubscriptions(permission)) {
                if (permissible instanceof CommandSender && permissible.hasPermission(permission)) {
                    recipients.add((CommandSender) permissible);
                }
            }
        }

        for (CommandSender recipient : recipients) {
            recipient.sendImportantMessage(message);
        }

        return recipients.size();
    }

    /**
     * ???????
     * @param players 
     * @param packet ?
     * @return void
     */
    public static void broadcastPacket(Collection<Player> players, DataPacket packet) {
        broadcastPacket(players.stream().toArray(Player[]::new), packet);
    }

    /**
     * ???????
     * @param players 
     * @param packet ?
     * @return void
     */
    public static void broadcastPacket(Player[] players, DataPacket packet) {
        packet.encode();
        packet.isEncoded = true;
        if (Network.BATCH_THRESHOLD >= 0 && packet.getBuffer().length >= Network.BATCH_THRESHOLD) {
            Server.getInstance().batchPackets(players, new DataPacket[] { packet }, false);
            return;
        }

        for (Player player : players) {
            player.dataPacket(packet);
        }

        if (packet.encapsulatedPacket != null) {
            packet.encapsulatedPacket = null;
        }
    }

    public void batchPackets(Player[] players, DataPacket[] packets) {
        this.batchPackets(players, packets, false);
    }

    public void batchPackets(Player[] players, DataPacket[] packets, boolean forceSync) {
        if (players == null || packets == null || players.length == 0 || packets.length == 0) {
            return;
        }

        Timings.playerNetworkSendTimer.startTiming();
        byte[][] payload = new byte[packets.length * 2][];
        for (int i = 0; i < packets.length; i++) {
            DataPacket p = packets[i];
            if (!p.isEncoded) {
                p.encode();
            }
            byte[] buf = p.getBuffer();
            payload[i * 2] = Binary.writeUnsignedVarInt(buf.length);
            payload[i * 2 + 1] = buf;
        }
        byte[] data;
        data = Binary.appendBytes(payload);

        List<String> targets = new ArrayList<>();
        for (Player p : players) {
            if (p.isConnected()) {
                targets.add(this.identifier.get(p.rawHashCode()));
            }
        }

        if (!forceSync && this.networkCompressionAsync) {
            this.getScheduler()
                    .scheduleAsyncTask(new CompressBatchedTask(data, targets, this.networkCompressionLevel));
        } else {
            try {
                this.broadcastPacketsCallback(Zlib.deflate(data, this.networkCompressionLevel), targets);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        Timings.playerNetworkSendTimer.stopTiming();
    }

    public void broadcastPacketsCallback(byte[] data, List<String> identifiers) {
        BatchPacket pk = new BatchPacket();
        pk.payload = data;
        pk.encode();
        pk.isEncoded = true;

        for (String i : identifiers) {
            if (this.players.containsKey(i)) {
                this.players.get(i).dataPacket(pk);
            }
        }
    }

    public void enablePlugins(PluginLoadOrder type) {
        for (Plugin plugin : new ArrayList<>(this.pluginManager.getPlugins().values())) {
            if (!plugin.isEnabled() && type == plugin.getDescription().getOrder()) {
                this.enablePlugin(plugin);
            }
        }

        if (type == PluginLoadOrder.POSTWORLD) {
            this.commandMap.registerServerAliases();
            DefaultPermissions.registerCorePermissions();
        }
    }

    public void enablePlugin(Plugin plugin) {
        this.pluginManager.enablePlugin(plugin);
    }

    public void disablePlugins() {
        this.pluginManager.disablePlugins();
    }

    /**
     * ???
     * @param sender ?CommandSender
     * @param commandLine ?
     * @return boolean true?/false?
     */
    public boolean dispatchCommand(CommandSender sender, String commandLine) throws ServerException {
        // First we need to check if this command is on the main thread or not, if not, warn the user
        if (!this.isPrimaryThread()) {
            getLogger().warning("Command Dispatched Async: " + commandLine);
            getLogger().warning("Please notify author of plugin causing this execution to fix this bug!",
                    new Throwable());
            // TODO: We should sync the command to the main thread too!
        }
        if (sender == null) {
            throw new ServerException("CommandSender is not valid");
        }

        if (this.commandMap.dispatch(sender, commandLine)) {
            return true;
        }

        sender.sendMessage(new TranslationContainer(TextFormat.RED + "%commands.generic.notFound"));

        return false;
    }

    //todo: use ticker to check console
    public ConsoleCommandSender getConsoleSender() {
        return consoleSender;
    }

    /**
     * ????????
     * @return void
     */
    public void reload() {
        this.logger.info("???...");

        this.logger.info("??????...");

        for (Level level : this.levels.values()) {
            level.save();
        }

        this.pluginManager.disablePlugins();
        this.pluginManager.clearPlugins();
        this.commandMap.clearCommands();

        this.logger.info("server.properties????????...");
        this.properties.reload();
        this.maxPlayers = this.getPropertyInt("max-players", 20);

        if (this.getPropertyBoolean("hardcore", false) && this.getDifficulty() < 3) {
            this.setPropertyInt("difficulty", 3);
        }

        this.banByIP.load();
        this.banByName.load();
        this.reloadWhitelist();
        this.operators.reload();

        for (BanEntry entry : this.getIPBans().getEntires().values()) {
            this.getNetwork().blockAddress(entry.getName(), -1);
        }

        this.pluginManager.registerInterface(JavaPluginLoader.class);
        this.pluginManager.loadPlugins(this.pluginPath);
        this.enablePlugins(PluginLoadOrder.STARTUP);
        this.enablePlugins(PluginLoadOrder.POSTWORLD);

        Timings.reset();
    }

    /**
     * ?????
     * @return void
     */
    public void shutdown() {
        if (this.isRunning) {
            ServerKiller killer = new ServerKiller(90);
            killer.start();
        }
        this.isRunning = false;
    }

    public void forceShutdown() {
        if (this.hasStopped) {
            return;
        }

        try {
            if (!this.isRunning) {
                //todo sendUsage
            }

            // clean shutdown of console thread asap
            this.console.shutdown();

            this.hasStopped = true;

            this.shutdown();

            if (this.rcon != null) {
                this.rcon.close();
            }

            this.getLogger().debug("Disabling all plugins");
            this.pluginManager.disablePlugins();

            for (Player player : new ArrayList<>(this.players.values())) {
                player.close(player.getLeaveMessage(),
                        (String) this.getConfig("settings.shutdown-message", "Server closed"));
            }

            this.getLogger().debug("Unloading all levels");
            for (Level level : new ArrayList<>(this.getLevels().values())) {
                this.unloadLevel(level, true);
            }

            this.getLogger().debug("Removing event handlers");
            HandlerList.unregisterAll();

            this.getLogger().debug("Stopping all tasks");
            this.scheduler.cancelAllTasks();
            this.scheduler.mainThreadHeartbeat(Integer.MAX_VALUE);

            this.getLogger().debug("Closing console");
            this.console.interrupt();

            this.getLogger().debug("Stopping network interfaces");
            for (SourceInterface interfaz : this.network.getInterfaces()) {
                interfaz.shutdown();
                this.network.unregisterInterface(interfaz);
            }

            this.getLogger().debug("Disabling timings");
            Timings.stopServer();
            //todo other things
        } catch (Exception e) {
            this.logger.logException(e); //todo remove this?
            this.logger.emergency("Exception happened while shutting down, exit the process");
            System.exit(1);
        }
    }

    /**
     * ?????
     * @return void
     */
    public void start() {
        if (this.getPropertyBoolean("enable-query", true)) {
            this.queryHandler = new QueryHandler();
        }

        for (BanEntry entry : this.getIPBans().getEntires().values()) {
            this.network.blockAddress(entry.getName(), -1);
        }

        //todo send usage setting

        this.tickCounter = 0;

        this.logger.info(this.getLanguage().translateString("nukkit.server.defaultGameMode",
                getGamemodeString(this.getGamemode())));

        this.logger.info(this.getLanguage().translateString("nukkit.server.startFinished",
                String.valueOf((double) (System.currentTimeMillis() - Nukkit.START_TIME) / 1000)));

        this.tickProcessor();
        this.forceShutdown();
    }

    public void handlePacket(String address, int port, byte[] payload) {
        try {
            if (payload.length > 2
                    && Arrays.equals(Binary.subBytes(payload, 0, 2), new byte[] { (byte) 0xfe, (byte) 0xfd })
                    && this.queryHandler != null) {
                this.queryHandler.handle(address, port, payload);
            }
        } catch (Exception e) {
            this.logger.logException(e);

            this.getNetwork().blockAddress(address, 600);
        }
    }

    public void tickProcessor() {
        this.nextTick = System.currentTimeMillis();
        try {
            while (this.isRunning) {
                try {
                    this.tick();

                    long next = this.nextTick;
                    long current = System.currentTimeMillis();

                    if (next - 0.1 > current) {
                        Thread.sleep(next - current - 1, 900000);
                    }
                } catch (RuntimeException e) {
                    this.getLogger().logException(e);
                }
            }
        } catch (Throwable e) {
            this.logger.emergency("Exception happened while ticking server");
            this.logger.alert(Utils.getExceptionMessage(e));
            this.logger.alert(Utils.getAllThreadDumps());
        }
    }

    public void onPlayerLogin(Player player) {
        if (this.sendUsageTicker > 0) {
            this.uniquePlayers.add(player.getUniqueId());
        }
    }

    public void addPlayer(String identifier, Player player) {
        this.players.put(identifier, player);
        this.identifier.put(player.rawHashCode(), identifier);
    }

    public void addOnlinePlayer(Player player) {
        this.playerList.put(player.getUniqueId(), player);
        this.updatePlayerListData(player.getUniqueId(), player.getId(), player.getDisplayName(), player.getSkin());
    }

    public void removeOnlinePlayer(Player player) {
        if (this.playerList.containsKey(player.getUniqueId())) {
            this.playerList.remove(player.getUniqueId());

            PlayerListPacket pk = new PlayerListPacket();
            pk.type = PlayerListPacket.TYPE_REMOVE;
            pk.entries = new PlayerListPacket.Entry[] { new PlayerListPacket.Entry(player.getUniqueId()) };

            Server.broadcastPacket(this.playerList.values(), pk);
        }
    }

    public void updatePlayerListData(UUID uuid, long entityId, String name, Skin skin) {
        this.updatePlayerListData(uuid, entityId, name, skin, this.playerList.values());
    }

    public void updatePlayerListData(UUID uuid, long entityId, String name, Skin skin, Player[] players) {
        PlayerListPacket pk = new PlayerListPacket();
        pk.type = PlayerListPacket.TYPE_ADD;
        pk.entries = new PlayerListPacket.Entry[] { new PlayerListPacket.Entry(uuid, entityId, name, skin) };
        Server.broadcastPacket(players, pk);
    }

    public void updatePlayerListData(UUID uuid, long entityId, String name, Skin skin, Collection<Player> players) {
        this.updatePlayerListData(uuid, entityId, name, skin,
                players.stream().filter(p -> !p.getUniqueId().equals(uuid)).toArray(Player[]::new));
    }

    public void removePlayerListData(UUID uuid) {
        this.removePlayerListData(uuid, this.playerList.values());
    }

    public void removePlayerListData(UUID uuid, Player[] players) {
        PlayerListPacket pk = new PlayerListPacket();
        pk.type = PlayerListPacket.TYPE_REMOVE;
        pk.entries = new PlayerListPacket.Entry[] { new PlayerListPacket.Entry(uuid) };
        Server.broadcastPacket(players, pk);
    }

    public void removePlayerListData(UUID uuid, Collection<Player> players) {
        this.removePlayerListData(uuid, players.stream().toArray(Player[]::new));
    }

    public void sendFullPlayerListData(Player player) {
        final UUID uuid = player.getUniqueId();
        PlayerListPacket pk = new PlayerListPacket();
        pk.type = PlayerListPacket.TYPE_ADD;
        pk.entries = this.playerList.values().stream().filter(p -> !p.getUniqueId().equals(uuid))
                .map(p -> new PlayerListPacket.Entry(p.getUniqueId(), p.getId(), p.getDisplayName(), p.getSkin()))
                .toArray(PlayerListPacket.Entry[]::new);

        player.dataPacket(pk);
    }

    public void sendRecipeList(Player player) {
        CraftingDataPacket pk = new CraftingDataPacket();
        pk.cleanRecipes = true;

        for (Recipe recipe : this.getCraftingManager().getRecipes().values()) {
            if (recipe instanceof ShapedRecipe) {
                pk.addShapedRecipe((ShapedRecipe) recipe);
            } else if (recipe instanceof ShapelessRecipe) {
                pk.addShapelessRecipe((ShapelessRecipe) recipe);
            }
        }

        for (FurnaceRecipe recipe : this.getCraftingManager().getFurnaceRecipes().values()) {
            pk.addFurnaceRecipe(recipe);
        }

        player.dataPacket(pk);
    }

    private void checkTickUpdates(int currentTick, long tickTime) {
        for (Player p : new ArrayList<>(this.players.values())) {
            /*if (!p.loggedIn && (tickTime - p.creationTime) >= 10000 && p.kick(PlayerKickEvent.Reason.LOGIN_TIMEOUT, "Login timeout")) {
            continue;
            }
                
            client freezes when applying resource packs
            todo: fix*/

            if (this.alwaysTickPlayers) {
                p.onUpdate(currentTick);
            }
        }

        //Do level ticks
        for (Level level : this.getLevels().values()) {
            if (level.getTickRate() > this.baseTickRate && --level.tickRateCounter > 0) {
                continue;
            }

            try {
                long levelTime = System.currentTimeMillis();
                level.doTick(currentTick);
                int tickMs = (int) (System.currentTimeMillis() - levelTime);
                level.tickRateTime = tickMs;

                if (this.autoTickRate) {
                    if (tickMs < 50 && level.getTickRate() > this.baseTickRate) {
                        int r;
                        level.setTickRate(r = level.getTickRate() - 1);
                        if (r > this.baseTickRate) {
                            level.tickRateCounter = level.getTickRate();
                        }
                        this.getLogger().debug("Raising level \"" + level.getName() + "\" tick rate to "
                                + level.getTickRate() + " ticks");
                    } else if (tickMs >= 50) {
                        if (level.getTickRate() == this.baseTickRate) {
                            level.setTickRate((int) Math.max(this.baseTickRate + 1,
                                    Math.min(this.autoTickRateLimit, Math.floor(tickMs / 50))));
                            this.getLogger()
                                    .debug("Level \"" + level.getName() + "\" took " + NukkitMath.round(tickMs, 2)
                                            + "ms, setting tick rate to " + level.getTickRate() + " ticks");
                        } else if ((tickMs / level.getTickRate()) >= 50
                                && level.getTickRate() < this.autoTickRateLimit) {
                            level.setTickRate(level.getTickRate() + 1);
                            this.getLogger()
                                    .debug("Level \"" + level.getName() + "\" took " + NukkitMath.round(tickMs, 2)
                                            + "ms, setting tick rate to " + level.getTickRate() + " ticks");
                        }
                        level.tickRateCounter = level.getTickRate();
                    }
                }
            } catch (Exception e) {
                if (Nukkit.DEBUG > 1 && this.logger != null) {
                    this.logger.logException(e);
                }

                this.logger.critical(this.getLanguage().translateString("nukkit.level.tickError",
                        new String[] { level.getName(), e.toString() }));
                this.logger.logException(e);
            }
        }
    }

    public void doAutoSave() {
        if (this.getAutoSave()) {
            Timings.levelSaveTimer.startTiming();
            for (Player player : new ArrayList<>(this.players.values())) {
                if (player.isOnline()) {
                    player.save(true);
                } else if (!player.isConnected()) {
                    this.removePlayer(player);
                }
            }

            for (Level level : this.getLevels().values()) {
                level.save();
            }
            Timings.levelSaveTimer.stopTiming();
        }
    }

    private boolean tick() {
        long tickTime = System.currentTimeMillis();
        long tickTimeNano = System.nanoTime();
        if ((tickTime - this.nextTick) < -25) {
            return false;
        }

        Timings.fullServerTickTimer.startTiming();

        ++this.tickCounter;

        Timings.connectionTimer.startTiming();
        this.network.processInterfaces();

        if (this.rcon != null) {
            this.rcon.check();
        }
        Timings.connectionTimer.stopTiming();

        Timings.schedulerTimer.startTiming();
        this.scheduler.mainThreadHeartbeat(this.tickCounter);
        Timings.schedulerTimer.stopTiming();

        this.checkTickUpdates(this.tickCounter, tickTime);

        for (Player player : new ArrayList<>(this.players.values())) {
            player.checkNetwork();
        }

        if ((this.tickCounter & 0b1111) == 0) {
            this.titleTick();
            this.maxTick = 20;
            this.maxUse = 0;

            if ((this.tickCounter & 0b111111111) == 0) {
                try {
                    this.getPluginManager()
                            .callEvent(this.queryRegenerateEvent = new QueryRegenerateEvent(this, 5));
                    if (this.queryHandler != null) {
                        this.queryHandler.regenerateInfo();
                    }
                } catch (Exception e) {
                    this.logger.logException(e);
                }
            }

            this.getNetwork().updateName();
        }

        if (this.autoSave && ++this.autoSaveTicker >= this.autoSaveTicks) {
            this.autoSaveTicker = 0;
            this.doAutoSave();
        }

        if (this.sendUsageTicker > 0 && --this.sendUsageTicker == 0) {
            this.sendUsageTicker = 6000;
            //todo sendUsage
        }

        if (this.tickCounter % 100 == 0) {
            for (Level level : this.levels.values()) {
                level.clearCache();
                level.doChunkGarbageCollection();
            }
        }

        Timings.fullServerTickTimer.stopTiming();
        //long now = System.currentTimeMillis();
        long nowNano = System.nanoTime();
        //float tick = Math.min(20, 1000 / Math.max(1, now - tickTime));
        //float use = Math.min(1, (now - tickTime) / 50);

        float tick = (float) Math.min(20, 1000000000 / Math.max(1000000, ((double) nowNano - tickTimeNano)));
        float use = (float) Math.min(1, ((double) (nowNano - tickTimeNano)) / 50000000);

        if (this.maxTick > tick) {
            this.maxTick = tick;
        }

        if (this.maxUse < use) {
            this.maxUse = use;
        }

        System.arraycopy(this.tickAverage, 1, this.tickAverage, 0, this.tickAverage.length - 1);
        this.tickAverage[this.tickAverage.length - 1] = tick;

        System.arraycopy(this.useAverage, 1, this.useAverage, 0, this.useAverage.length - 1);
        this.useAverage[this.useAverage.length - 1] = use;

        if ((this.nextTick - tickTime) < -1000) {
            this.nextTick = tickTime;
        } else {
            this.nextTick += 50;
        }

        return true;
    }

    // TODO: Fix title tick
    public void titleTick() {
        if (true || !Nukkit.ANSI) {
            return;
        }

        Runtime runtime = Runtime.getRuntime();
        double used = NukkitMath.round((double) (runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024, 2);
        double max = NukkitMath.round(((double) runtime.maxMemory()) / 1024 / 1024, 2);
        String usage = Math.round(used / max * 100) + "%";
        String title = (char) 0x1b + "]0;" + this.getName() + " " + this.getNukkitVersion() + " | Online "
                + this.players.size() + "/" + this.getMaxPlayers() + " | Memory " + usage;
        if (!Nukkit.shortTitle) {
            title += " | U " + NukkitMath.round((this.network.getUpload() / 1024 * 1000), 2) + " D "
                    + NukkitMath.round((this.network.getDownload() / 1024 * 1000), 2) + " kB/s";
        }
        title += " | TPS " + this.getTicksPerSecond() + " | Load " + this.getTickUsage() + "%" + (char) 0x07;

        System.out.print(title);

        this.network.resetStatistics();
    }

    public QueryRegenerateEvent getQueryInformation() {
        return this.queryRegenerateEvent;
    }

    /**
     * ?????????
     * @return String ????"Nukkit"???????
     */
    public String getName() {
        return "Nukkit";
    }

    public boolean isRunning() {
        return isRunning;
    }

    /**
     * Nukkit??????
     * @return String Nukkit?
     */
    public String getNukkitVersion() {
        return Nukkit.VERSION;
    }

    /**
     * ?????
     * @return String ?
     */
    public String getCodename() {
        return Nukkit.CODENAME;
    }

    /**
     * PE??????
     * @return String Minecraft?
     */
    public String getVersion() {
        return ProtocolInfo.MINECRAFT_VERSION;
    }

    /**
     * API?????
     * @return String API?
     */
    public String getApiVersion() {
        return Nukkit.API_VERSION;
    }

    /**
     * ????
     * @return String 
     */
    public String getFilePath() {
        return filePath;
    }

    /**
     * ????
     * <br>????jar????
     * @return String 
     */
    public String getDataPath() {
        return dataPath;
    }

    /**
     * plugins?????
     * <br>????getDataPath()??/plugins?????????
     * @return String ?
     * @see Server#getDataPath()
     */
    public String getPluginPath() {
        return pluginPath;
    }

    public String getDefaultplugins() {
        return defaultplugin;
    }

    /**
     * ????????
     * @return int ??
     */
    public int getMaxPlayers() {
        return maxPlayers;
    }

    /**
     * ???????
     * <br>server.properties?server-port???
     * @return int ?
     */
    public int getPort() {
        return this.getPropertyInt("server-port", 19132);
    }

    /**
     * ?????????
     * <br>server.properties?view-distancet???
     * @return int ???
     */
    public int getViewDistance() {
        return this.getPropertyInt("view-distance", 10);
    }

    /**
     * ??IP????
     * <br>server.properties?server-ip???
     * @return String IP
     */
    public String getIp() {
        return this.getPropertyString("server-ip", "0.0.0.0");
    }

    public UUID getServerUniqueId() {
        return this.serverID;
    }

    /**
     * ???????????
     * @return boolean true?/false?
     */
    public boolean getAutoSave() {
        return this.autoSave;
    }

    /**
     * ?????
     * @param autoSave true?/false?
     */
    public void setAutoSave(boolean autoSave) {
        this.autoSave = autoSave;
        for (Level level : this.getLevels().values()) {
            level.setAutoSave(this.autoSave);
        }
    }

    /**
     * ??????
     * <br>server.properties?level-type???
     * <br>
     * <br>[]
     * <br>FLAT 
     * <br>DEFAULT 
     * @return String 
     */
    public String getLevelType() {
        return this.getPropertyString("level-type", "DEFAULT");
    }

    public boolean getGenerateStructures() {
        return this.getPropertyBoolean("generate-structures", true);
    }

    /**
     * @deprecated {@link Server#getDefaultGamemode()}????????
     * <br>??????
     * <br>server.properties?gamemode???
     * <br>
     * <br>[]
     * <br>0:??
     * <br>1:
     * <br>2:?
     * <br>3:
     * @return int 
     */
    public int getGamemode() {
        return this.getPropertyInt("gamemode", 0) & 0b11;
    }

    public boolean getForceGamemode() {
        return this.getPropertyBoolean("force-gamemode", false);
    }

    /**
     * ??????????
     * <br>server.properties?gamemode???
     * <br>
     * <br>[???????:(???)]
     * <br>??(0)
     * <br>(1)
     * <br>?(2)
     * <br>(3)
     * <br>UNKNOWN(0-3????)
     * @param mode ??????????(0, 1, 2, 3)
     * @return String 
     */
    public static String getGamemodeString(int mode) {
        switch (mode) {
        case Player.SURVIVAL:
            return "%gameMode.survival";
        case Player.CREATIVE:
            return "%gameMode.creative";
        case Player.ADVENTURE:
            return "%gameMode.adventure";
        case Player.SPECTATOR:
            return "%gameMode.spectator";
        }
        return "UNKNOWN";
    }

    public static int getGamemodeFromString(String str) {
        switch (str.trim().toLowerCase()) {
        case "0":
        case "survival":
        case "s":
            return Player.SURVIVAL;

        case "1":
        case "creative":
        case "c":
            return Player.CREATIVE;

        case "2":
        case "adventure":
        case "a":
            return Player.ADVENTURE;

        case "3":
        case "spectator":
        case "spc":
        case "view":
        case "v":
            return Player.SPECTATOR;
        }
        return -1;
    }

    public static int getDifficultyFromString(String str) {
        switch (str.trim().toLowerCase()) {
        case "0":
        case "peaceful":
        case "p":
            return 0;

        case "1":
        case "easy":
        case "e":
            return 1;

        case "2":
        case "normal":
        case "n":
            return 2;

        case "3":
        case "hard":
        case "h":
            return 3;
        }
        return -1;
    }

    /**
     * ??????
     * <br>server.properties?difficulty???
     * @return int 
     */
    public int getDifficulty() {
        return this.getPropertyInt("difficulty", 1);
    }

    /**
     * ??????????
     * <br>server.properties?white-list???
     * @return boolean true?/false?
     */
    public boolean hasWhitelist() {
        return this.getPropertyBoolean("white-list", false);
    }

    /**
     * ??????????????
     * <br>server.properties?spawn-protection???
     * @return int ?
     */
    public int getSpawnRadius() {
        return this.getPropertyInt("spawn-protection", 16);
    }

    public boolean getAllowFlight() {
        if (getAllowFlight == null) {
            getAllowFlight = this.getPropertyBoolean("allow-flight", false);
        }
        return getAllowFlight;
    }

    /**
     * ???????????
     * <br>server.properties?hardcore???
     * @return boolean true?/false?
     */
    public boolean isHardcore() {
        return this.getPropertyBoolean("hardcore", false);
    }

    /**
     * ???????
     * <br>server.properties?gamemode???
     * <br>
     * <br>[]
     * <br>0:??
     * <br>1:
     * <br>2:?
     * <br>3:
     * @return int 
     */
    public int getDefaultGamemode() {
        return this.getPropertyInt("gamemode", 0);
    }

    /**
     * ???????
     * <br>server.properties?motd???
     * @return String 
     */
    public String getMotd() {
        return this.getPropertyString("motd", "Nukkit Server For Minecraft: PE");
    }

    public boolean getForceResources() {
        return this.getPropertyBoolean("force-resources", false);
    }

    /**
     * MainLogger????
     * @return MainLogger
     */
    public MainLogger getLogger() {
        return this.logger;
    }

    public EntityMetadataStore getEntityMetadata() {
        return entityMetadata;
    }

    public PlayerMetadataStore getPlayerMetadata() {
        return playerMetadata;
    }

    public LevelMetadataStore getLevelMetadata() {
        return levelMetadata;
    }

    /**
     * ?????
     * <br>
     * <br>[???: Itsu?]
     * <br>
     * <br>{@code this.getLogger().getPluginManager().registerEvents(this, this);}
     * @return PluginManager
     * @see PluginManager#registerEvents(cn.nukkit.event.Listener, Plugin)
     */
    public PluginManager getPluginManager() {
        return this.pluginManager;
    }

    /**
     * ?????
     * @return CraftingManager
     */
    public CraftingManager getCraftingManager() {
        return craftingManager;
    }

    /**
     * ?????
     * @return ResourcePackManager
     */
    public ResourcePackManager getResourcePackManager() {
        return resourcePackManager;
    }

    /**
     * ????
     * <br>
     * <br>[??:Itsu?]
     * <pre>
     * ?
     * {@code
     *  TaskHandler th;
     *  th = this.getServer().getScheduler().scheduleRepeatingTask(null, new Runnable(){
     *      //??
     *  };, tick(int));
     *
     *
     * ?????
     *  TaskHandler th;
     *  th = this.getServer().getScheduler().scheduleDelayedRepeatingTask(null, new Runnable(){
     *      //??
     *  };, ?tick(int), tick(int));
     *
     *
     *  ?
     *  th.cancel();
     * </pre>
     * []
     * <br>20tick = 1??!
     * @return ServerScheduler
     */
    public ServerScheduler getScheduler() {
        return scheduler;
    }

    public int getTick() {
        return tickCounter;
    }

    public float getTicksPerSecond() {
        return ((float) Math.round(this.maxTick * 100)) / 100;
    }

    public float getTicksPerSecondAverage() {
        float sum = 0;
        int count = this.tickAverage.length;
        for (float aTickAverage : this.tickAverage) {
            sum += aTickAverage;
        }
        return (float) NukkitMath.round(sum / count, 2);
    }

    public float getTickUsage() {
        return (float) NukkitMath.round(this.maxUse * 100, 2);
    }

    public float getTickUsageAverage() {
        float sum = 0;
        int count = this.useAverage.length;
        for (float aUseAverage : this.useAverage) {
            sum += aUseAverage;
        }
        return ((float) Math.round(sum / count * 100)) / 100;
    }

    public SimpleCommandMap getCommandMap() {
        return commandMap;
    }

    public Map<UUID, Player> getOnlinePlayers() {
        return new HashMap<>(playerList);
    }

    public void addRecipe(Recipe recipe) {
        this.craftingManager.registerRecipe(recipe);
    }

    public IPlayer getOfflinePlayer(String name) {
        IPlayer result = this.getPlayerExact(name.toLowerCase());
        if (result == null) {
            return new OfflinePlayer(this, name);
        }

        return result;
    }

    public CompoundTag getOfflinePlayerData(String name) {
        name = name.toLowerCase();
        String path = this.getDataPath() + "players/";
        File file = new File(path + name + ".dat");

        if (this.shouldSavePlayerData() && file.exists()) {
            try {
                return NBTIO.readCompressed(new FileInputStream(file));
            } catch (Exception e) {
                file.renameTo(new File(path + name + ".dat.bak"));
                this.logger.notice(this.getLanguage().translateString("nukkit.data.playerCorrupted", name));
            }
        } else {
            this.logger.notice(this.getLanguage().translateString("nukkit.data.playerNotFound", name));
        }

        Position spawn = this.getDefaultLevel().getSafeSpawn();
        CompoundTag nbt = new CompoundTag().putLong("firstPlayed", System.currentTimeMillis() / 1000)
                .putLong("lastPlayed", System.currentTimeMillis() / 1000)
                .putList(new ListTag<DoubleTag>("Pos").add(new DoubleTag("0", spawn.x))
                        .add(new DoubleTag("1", spawn.y)).add(new DoubleTag("2", spawn.z)))
                .putString("Level", this.getDefaultLevel().getName()).putList(new ListTag<>("Inventory"))
                .putCompound("Achievements", new CompoundTag()).putInt("playerGameType", this.getGamemode())
                .putList(new ListTag<DoubleTag>("Motion").add(new DoubleTag("0", 0)).add(new DoubleTag("1", 0))
                        .add(new DoubleTag("2", 0)))
                .putList(new ListTag<FloatTag>("Rotation").add(new FloatTag("0", 0)).add(new FloatTag("1", 0)))
                .putFloat("FallDistance", 0).putShort("Fire", 0).putShort("Air", 300).putBoolean("OnGround", true)
                .putBoolean("Invulnerable", false).putString("NameTag", name);

        this.saveOfflinePlayerData(name, nbt);
        return nbt;
    }

    public void saveOfflinePlayerData(String name, CompoundTag tag) {
        this.saveOfflinePlayerData(name, tag, false);
    }

    public void saveOfflinePlayerData(String name, CompoundTag tag, boolean async) {
        if (this.shouldSavePlayerData()) {
            try {
                if (async) {
                    this.getScheduler().scheduleAsyncTask(
                            new FileWriteTask(this.getDataPath() + "players/" + name.toLowerCase() + ".dat",
                                    NBTIO.writeGZIPCompressed(tag, ByteOrder.BIG_ENDIAN)));
                } else {
                    Utils.writeFile(this.getDataPath() + "players/" + name.toLowerCase() + ".dat",
                            new ByteArrayInputStream(NBTIO.writeGZIPCompressed(tag, ByteOrder.BIG_ENDIAN)));
                }
            } catch (Exception e) {
                this.logger.critical(this.getLanguage().translateString("nukkit.data.saveError",
                        new String[] { name, e.getMessage() }));
                if (Nukkit.DEBUG > 1) {
                    this.logger.logException(e);
                }
            }
        }
    }

    /**
     * ????????
     * @param name ????????
     * @return Player ???
     */
    public Player getPlayer(String name) {
        Player found = null;
        name = name.toLowerCase();
        int delta = Integer.MAX_VALUE;
        for (Player player : this.getOnlinePlayers().values()) {
            if (player.getName().toLowerCase().startsWith(name)) {
                int curDelta = player.getName().length() - name.length();
                if (curDelta < delta) {
                    found = player;
                    delta = curDelta;
                }
                if (curDelta == 0) {
                    break;
                }
            }
        }

        return found;
    }

    public Player getPlayerExact(String name) {
        name = name.toLowerCase();
        for (Player player : this.getOnlinePlayers().values()) {
            if (player.getName().toLowerCase().equals(name)) {
                return player;
            }
        }

        return null;
    }

    public Player[] matchPlayer(String partialName) {
        partialName = partialName.toLowerCase();
        List<Player> matchedPlayer = new ArrayList<>();
        for (Player player : this.getOnlinePlayers().values()) {
            if (player.getName().toLowerCase().equals(partialName)) {
                return new Player[] { player };
            } else if (player.getName().toLowerCase().contains(partialName)) {
                matchedPlayer.add(player);
            }
        }

        return matchedPlayer.toArray(new Player[matchedPlayer.size()]);
    }

    public void removePlayer(Player player) {
        if (this.identifier.containsKey(player.rawHashCode())) {
            String identifier = this.identifier.get(player.rawHashCode());
            this.players.remove(identifier);
            this.identifier.remove(player.rawHashCode());
            return;
        }

        for (String identifier : new ArrayList<>(this.players.keySet())) {
            Player p = this.players.get(identifier);
            if (player == p) {
                this.players.remove(identifier);
                this.identifier.remove(player.rawHashCode());
                break;
            }
        }
    }

    public Map<Integer, Level> getLevels() {
        return levels;
    }

    /**
     * ?????????
     * @return Level ?????
     */
    public Level getDefaultLevel() {
        return defaultLevel;
    }

    public void setDefaultLevel(Level defaultLevel) {
        if (defaultLevel == null
                || (this.isLevelLoaded(defaultLevel.getFolderName()) && defaultLevel != this.defaultLevel)) {
            this.defaultLevel = defaultLevel;
        }
    }

    public boolean isLevelLoaded(String name) {
        return this.getLevelByName(name) != null;
    }

    public Level getLevel(int levelId) {
        if (this.levels.containsKey(levelId)) {
            return this.levels.get(levelId);
        }
        return null;
    }

    /**
     * ????????
     * @param name ????????
     * @return Level ???
     */
    public Level getLevelByName(String name) {
        for (Level level : this.getLevels().values()) {
            if (level.getFolderName().equals(name)) {
                return level;
            }
        }

        return null;
    }

    public boolean unloadLevel(Level level) {
        return this.unloadLevel(level, false);
    }

    public boolean unloadLevel(Level level, boolean forceUnload) {
        if (level == this.getDefaultLevel() && !forceUnload) {
            throw new IllegalStateException(
                    "The default level cannot be unloaded while running, please switch levels.");
        }

        return level.unload(forceUnload);

    }

    public boolean loadLevel(String name) {
        if (Objects.equals(name.trim(), "")) {
            throw new LevelException("Invalid empty level name");
        }
        if (this.isLevelLoaded(name)) {
            return true;
        } else if (!this.isLevelGenerated(name)) {
            this.logger.notice(this.getLanguage().translateString("nukkit.level.notFound", name));

            return false;
        }

        String path;

        if (name.contains("/") || name.contains("\\")) {
            path = name;
        } else {
            path = this.getDataPath() + "worlds/" + name + "/";
        }

        Class<? extends LevelProvider> provider = LevelProviderManager.getProvider(path);

        if (provider == null) {
            this.logger.error(this.getLanguage().translateString("nukkit.level.loadError",
                    new String[] { name, "Unknown provider" }));

            return false;
        }

        Level level;
        try {
            level = new Level(this, name, path, provider);
        } catch (Exception e) {
            this.logger.error(this.getLanguage().translateString("nukkit.level.loadError",
                    new String[] { name, e.getMessage() }));
            this.logger.logException(e);
            return false;
        }

        this.levels.put(level.getId(), level);

        level.initLevel();

        this.getPluginManager().callEvent(new LevelLoadEvent(level));

        level.setTickRate(this.baseTickRate);

        return true;
    }

    public boolean generateLevel(String name) {
        return this.generateLevel(name, new java.util.Random().nextLong());
    }

    public boolean generateLevel(String name, long seed) {
        return this.generateLevel(name, seed, null);
    }

    public boolean generateLevel(String name, long seed, Class<? extends Generator> generator) {
        return this.generateLevel(name, seed, generator, new HashMap<>());
    }

    public boolean generateLevel(String name, long seed, Class<? extends Generator> generator,
            Map<String, Object> options) {
        return generateLevel(name, seed, generator, options, null);
    }

    public boolean generateLevel(String name, long seed, Class<? extends Generator> generator,
            Map<String, Object> options, Class<? extends LevelProvider> provider) {
        if (Objects.equals(name.trim(), "") || this.isLevelGenerated(name)) {
            return false;
        }

        if (!options.containsKey("preset")) {
            options.put("preset", this.getPropertyString("generator-settings", ""));
        }

        if (generator == null) {
            generator = Generator.getGenerator(this.getLevelType());
        }

        if (provider == null) {
            if ((provider = LevelProviderManager.getProviderByName(
                    (String) this.getConfig("level-settings.default-format", "anvil"))) == null) {
                provider = LevelProviderManager.getProviderByName("anvil");
            }
        }

        String path;

        if (name.contains("/") || name.contains("\\")) {
            path = name;
        } else {
            path = this.getDataPath() + "worlds/" + name + "/";
        }

        Level level;
        try {
            provider.getMethod("generate", String.class, String.class, long.class, Class.class, Map.class)
                    .invoke(null, path, name, seed, generator, options);

            level = new Level(this, name, path, provider);
            this.levels.put(level.getId(), level);

            level.initLevel();

            level.setTickRate(this.baseTickRate);
        } catch (Exception e) {
            this.logger.error(this.getLanguage().translateString("nukkit.level.generationError",
                    new String[] { name, e.getMessage() }));
            this.logger.logException(e);
            return false;
        }

        this.getPluginManager().callEvent(new LevelInitEvent(level));

        this.getPluginManager().callEvent(new LevelLoadEvent(level));

        /*this.getLogger().notice(this.getLanguage().translateString("nukkit.level.backgroundGeneration", name));
            
        int centerX = (int) level.getSpawnLocation().getX() >> 4;
        int centerZ = (int) level.getSpawnLocation().getZ() >> 4;
            
        TreeMap<String, Integer> order = new TreeMap<>();
            
        for (int X = -3; X <= 3; ++X) {
        for (int Z = -3; Z <= 3; ++Z) {
            int distance = X * X + Z * Z;
            int chunkX = X + centerX;
            int chunkZ = Z + centerZ;
            order.put(Level.chunkHash(chunkX, chunkZ), distance);
        }
        }
            
        List<Map.Entry<String, Integer>> sortList = new ArrayList<>(order.entrySet());
            
        Collections.sort(sortList, new Comparator<Map.Entry<String, Integer>>() {
        @Override
        public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
            return o2.getValue() - o1.getValue();
        }
        });
            
        for (String index : order.keySet()) {
        Chunk.Entry entry = Level.getChunkXZ(index);
        level.populateChunk(entry.chunkX, entry.chunkZ, true);
        }*/

        return true;
    }

    public boolean isLevelGenerated(String name) {
        if (Objects.equals(name.trim(), "")) {
            return false;
        }

        String path = this.getDataPath() + "worlds/" + name + "/";
        if (this.getLevelByName(name) == null) {

            if (LevelProviderManager.getProvider(path) == null) {
                return false;
            }
        }

        return true;
    }

    public BaseLang getLanguage() {
        return baseLang;
    }

    public boolean isLanguageForced() {
        return forceLanguage;
    }

    public Network getNetwork() {
        return network;
    }

    //Revising later...
    public Config getConfig() {
        return this.config;
    }

    public Object getConfig(String variable) {
        return this.getConfig(variable, null);
    }

    public Object getConfig(String variable, Object defaultValue) {
        Object value = this.config.get(variable);
        return value == null ? defaultValue : value;
    }

    /**
     * server.properties?(Config)????
     * @return Config ?
     */
    public Config getProperties() {
        return this.properties;
    }

    /**
     * server.properties?????????
     * <br>????????????????
     * @param variable 
     * @return Object ???
     */
    public Object getProperty(String variable) {
        return this.getProperty(variable, null);
    }

    public Object getProperty(String variable, Object defaultValue) {
        return this.properties.exists(variable) ? this.properties.get(variable) : defaultValue;
    }

    /**
     * server.properties?????String????
     * @param variable 
     * @param value 
     * @return void
     */
    public void setPropertyString(String variable, String value) {
        this.properties.set(variable, value);
        this.properties.save();
    }

    /**
     * server.properties?????String?????
     * @param variable 
     * @return String ???
     */
    public String getPropertyString(String variable) {
        return this.getPropertyString(variable, null);
    }

    public String getPropertyString(String variable, String defaultValue) {
        return this.properties.exists(variable) ? (String) this.properties.get(variable) : defaultValue;
    }

    /**
     * server.properties?????int?????
     * @param variable 
     * @return int ???
     */
    public int getPropertyInt(String variable) {
        return this.getPropertyInt(variable, null);
    }

    public int getPropertyInt(String variable, Integer defaultValue) {
        return this.properties.exists(variable) ? (!this.properties.get(variable).equals("")
                ? Integer.parseInt(String.valueOf(this.properties.get(variable)))
                : defaultValue) : defaultValue;
    }

    /**
     * server.properties?????int????
     * @param variable 
     * @param value 
     * @return void
     */
    public void setPropertyInt(String variable, int value) {
        this.properties.set(variable, value);
        this.properties.save();
    }

    /**
     * server.properties?????boolean?????
     * @param variable 
     * @return boolean ???
     */
    public boolean getPropertyBoolean(String variable) {
        return this.getPropertyBoolean(variable, null);
    }

    public boolean getPropertyBoolean(String variable, Object defaultValue) {
        Object value = this.properties.exists(variable) ? this.properties.get(variable) : defaultValue;
        if (value instanceof Boolean) {
            return (Boolean) value;
        }
        switch (String.valueOf(value)) {
        case "on":
        case "true":
        case "1":
        case "yes":
            return true;
        }
        return false;
    }

    /**
     * server.properties?????boolean????
     * @param variable 
     * @param value 
     * @return void
     */
    public void setPropertyBoolean(String variable, boolean value) {
        this.properties.set(variable, value ? "1" : "0");
        this.properties.save();
    }

    /**
     * jupiter.yml?(Config)????
     * @return Config jupiter.yml?
     */
    public Config getJupiterConfig() {
        return new Config(this.getDataPath() + "jupiter.yml");
    }

    public boolean isLoadedJupiterConfig() {
        return !this.jupiterconfig.isEmpty();
    }

    /**
     * jupiter.yml???
     * @return void
     */
    public void loadJupiterConfig() {
        this.jupiterconfig = this.getJupiterConfig().getAll();
    }

    /**
     * jupiter.yml?????String?????
     * @param key 
     * @return String ???
     */
    public String getJupiterConfigString(String key) {
        return (String) this.jupiterconfig.get(key);
    }

    /**
     * jupiter.yml?????int?????
     * @param key 
     * @return int ???
     */
    public int getJupiterConfigInt(String key) {
        return (int) this.jupiterconfig.get(key);
    }

    /**
     * jupiter.yml?????Boolean?????
     * <br>?:?boolean???
     * @param key 
     * @return Boolean ???
     */
    public Boolean getJupiterConfigBoolean(String key) {
        return (Boolean) this.jupiterconfig.get(key);
    }

    public PluginIdentifiableCommand getPluginCommand(String name) {
        Command command = this.commandMap.getCommand(name);
        if (command instanceof PluginIdentifiableCommand) {
            return (PluginIdentifiableCommand) command;
        } else {
            return null;
        }
    }

    /**
     * ???Ban???????
     * @return BanList ???Ban
     */
    public BanList getNameBans() {
        return this.banByName;
    }

    /**
     * IPBan???????
     * @return BanList IPBan
     */
    public BanList getIPBans() {
        return this.banByIP;
    }

    /**
     * ???????OP????
     * @param name OP????????
     * @return void
     */
    public void addOp(String name) {
        this.operators.set(name.toLowerCase(), true);
        Player player = this.getPlayerExact(name);
        if (player != null) {
            player.recalculatePermissions();
        }
        this.operators.save(true);
    }

    /**
     * ????????OP???
     * @param name OP???????
     * @return void
     */
    public void removeOp(String name) {
        this.operators.remove(name.toLowerCase());
        Player player = this.getPlayerExact(name);
        if (player != null) {
            player.recalculatePermissions();
        }
        this.operators.save();
    }

    /**
     * ????????????
     * @param name ????????
     * @return void
     */
    public void addWhitelist(String name) {
        this.whitelist.set(name.toLowerCase(), true);
        this.whitelist.save(true);
    }

    /**
     * ???????????
     * @param name ????????
     * @return void
     */
    public void removeWhitelist(String name) {
        this.whitelist.remove(name.toLowerCase());
        this.whitelist.save(true);
    }

    /**
     * ????????????????????
     * @param name ???????
     * @return boolean true????/false??????
     */
    public boolean isWhitelisted(String name) {
        return !this.hasWhitelist() || this.operators.exists(name, true) || this.whitelist.exists(name, true);
    }

    /**
     * ????????OP????????
     * @param name ???????
     * @return boolean true?OP/false??OP
     */
    public boolean isOp(String name) {
        return this.operators.exists(name, true);
    }

    public Config getWhitelist() {
        return whitelist;
    }

    public Config getOps() {
        return operators;
    }

    public void reloadWhitelist() {
        this.whitelist.reload();
    }

    public ServiceManager getServiceManager() {
        return serviceManager;
    }

    public Map<String, List<String>> getCommandAliases() {
        Object section = this.getConfig("aliases");
        Map<String, List<String>> result = new LinkedHashMap<>();
        if (section instanceof Map) {
            for (Map.Entry entry : (Set<Map.Entry>) ((Map) section).entrySet()) {
                List<String> commands = new ArrayList<>();
                String key = (String) entry.getKey();
                Object value = entry.getValue();
                if (value instanceof List) {
                    for (String string : (List<String>) value) {
                        commands.add(string);
                    }
                } else {
                    commands.add((String) value);
                }

                result.put(key, commands);
            }
        }

        return result;

    }

    public boolean shouldSavePlayerData() {
        return (Boolean) this.getConfig("player.save-player-data", true);
    }

    /**
     * Checks the current thread against the expected primary thread for the server.
     *
     * <b>Note:</b> this method should not be used to indicate the current synchronized state of the runtime. A current thread matching the main thread indicates that it is synchronized, but a mismatch does not preclude the same assumption.
     * @return true if the current thread matches the expected primary thread, false otherwise
     */
    public boolean isPrimaryThread() {
        return (Thread.currentThread() == currentThread);
    }

    private void registerEntities() {
        Entity.registerEntity("Arrow", EntityArrow.class);
        Entity.registerEntity("Item", EntityItem.class);
        Entity.registerEntity("FallingSand", EntityFallingBlock.class);
        Entity.registerEntity("PrimedTnt", EntityPrimedTNT.class);
        Entity.registerEntity("Snowball", EntitySnowball.class);
        Entity.registerEntity("EnderPearl", EntityEnderPearl.class);
        Entity.registerEntity("Painting", EntityPainting.class);
        Entity.registerEntity("FishingHook", EntityFishingHook.class);
        Entity.registerEntity("EnderCrystal", EntityEnderCrystal.class);
        //Mob
        Entity.registerEntity("Blaze", EntityBlaze.class);
        Entity.registerEntity("CaveSpider", EntityCaveSpider.class);
        Entity.registerEntity("Creeper", EntityCreeper.class);
        Entity.registerEntity("Enderman", EntityEnderman.class);
        Entity.registerEntity("Endermite", EntityEndermite.class);
        Entity.registerEntity("Ghast", EntityGhast.class);
        Entity.registerEntity("Guardian", EntityGuardian.class);
        Entity.registerEntity("Hask", EntityHask.class);
        Entity.registerEntity("MagmaCube", EntityMagmaCube.class);
        Entity.registerEntity("Shulker", EntityShulker.class);
        Entity.registerEntity("Silverfish", EntitySilverfish.class);
        Entity.registerEntity("Skeleton", EntitySkeleton.class);
        Entity.registerEntity("Slime", EntitySlime.class);
        Entity.registerEntity("Spider", EntitySpider.class);
        Entity.registerEntity("Stray", EntityStray.class);
        Entity.registerEntity("Witch", EntityWitch.class);
        Entity.registerEntity("WitherSkeleton", EntityWitherSkeleton.class);
        Entity.registerEntity("Zombie", EntityZombie.class);
        Entity.registerEntity("ZombiePigman", EntityZombiePigman.class);
        Entity.registerEntity("ZombieVillager", EntityZombieVillager.class);
        //TODO: more mobs
        Entity.registerEntity("Bat", EntityBat.class);
        Entity.registerEntity("Chicken", EntityChicken.class);
        Entity.registerEntity("Cow", EntityCow.class);
        Entity.registerEntity("Donkey", EntityDonkey.class);
        Entity.registerEntity("Horse", EntityHorse.class);
        Entity.registerEntity("Mooshroom", EntityMooshroom.class);
        Entity.registerEntity("Mule", EntityMule.class);
        Entity.registerEntity("Ocelot", EntityOcelot.class);
        Entity.registerEntity("Pig", EntityPig.class);
        Entity.registerEntity("PolarBear", EntityPolarBear.class);
        Entity.registerEntity("Rabbit", EntityRabbit.class);
        Entity.registerEntity("Sheep", EntitySheep.class);
        Entity.registerEntity("SkeletonHorse", EntitySkeletonHorse.class);
        Entity.registerEntity("Squid", EntitySquid.class);
        Entity.registerEntity("Villager", EntityVillager.class);
        Entity.registerEntity("Wolf", EntityWolf.class);
        Entity.registerEntity("ZombieHorse", EntityZombieHorse.class);
        //Bosses
        Entity.registerEntity("ElderGuardian", EntityElderGuardian.class);
        Entity.registerEntity("EnderDragon", EntityEnderDragon.class);
        Entity.registerEntity("EnderWither", EntityWither.class);

        Entity.registerEntity("ThrownExpBottle", EntityExpBottle.class);
        Entity.registerEntity("XpOrb", EntityXPOrb.class);
        Entity.registerEntity("ThrownPotion", EntityPotion.class);

        Entity.registerEntity("Human", EntityHuman.class, true);

        Entity.registerEntity("MinecartRideable", EntityMinecartEmpty.class);
        Entity.registerEntity("MinecartChest", EntityMinecartChest.class);
        Entity.registerEntity("MinecartHopper", EntityMinecartHopper.class);
        Entity.registerEntity("MinecartTnt", EntityMinecartTNT.class);
        Entity.registerEntity("Boat", EntityBoat.class);

        Entity.registerEntity("Lightning", EntityLightning.class);
    }

    private void registerBlockEntities() {
        BlockEntity.registerBlockEntity(BlockEntity.FURNACE, BlockEntityFurnace.class);
        BlockEntity.registerBlockEntity(BlockEntity.CHEST, BlockEntityChest.class);
        BlockEntity.registerBlockEntity(BlockEntity.SIGN, BlockEntitySign.class);
        BlockEntity.registerBlockEntity(BlockEntity.ENCHANT_TABLE, BlockEntityEnchantTable.class);
        BlockEntity.registerBlockEntity(BlockEntity.SKULL, BlockEntitySkull.class);
        BlockEntity.registerBlockEntity(BlockEntity.FLOWER_POT, BlockEntityFlowerPot.class);
        BlockEntity.registerBlockEntity(BlockEntity.BREWING_STAND, BlockEntityBrewingStand.class);
        BlockEntity.registerBlockEntity(BlockEntity.ITEM_FRAME, BlockEntityItemFrame.class);
        BlockEntity.registerBlockEntity(BlockEntity.CAULDRON, BlockEntityCauldron.class);
        BlockEntity.registerBlockEntity(BlockEntity.ENDER_CHEST, BlockEntityEnderChest.class);
        BlockEntity.registerBlockEntity(BlockEntity.BEACON, BlockEntityBeacon.class);
    }

    /**
     * ??????
     * @return Server ??
     */
    public static Server getInstance() {
        return instance;
    }

}