org.diorite.impl.DioriteCore.java Source code

Java tutorial

Introduction

Here is the source code for org.diorite.impl.DioriteCore.java

Source

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2016. Diorite (by Bartomiej Mazur (aka GotoFinal))
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package org.diorite.impl;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.net.Proxy;
import java.security.KeyPair;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.stream.Collectors;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.appender.ConsoleAppender;
import org.fusesource.jansi.AnsiConsole;
import org.reflections.Reflections;
import org.slf4j.LoggerFactory;

import org.diorite.impl.auth.SessionService;
import org.diorite.impl.auth.yggdrasil.YggdrasilSessionService;
import org.diorite.impl.cfg.DioriteConfigImpl;
import org.diorite.impl.command.ColoredConsoleCommandSenderImpl;
import org.diorite.impl.command.CommandMapImpl;
import org.diorite.impl.command.ConsoleCommandSenderImpl;
import org.diorite.impl.command.PluginCommandBuilderImpl;
import org.diorite.impl.command.defaults.RegisterDefaultCommands;
import org.diorite.impl.connection.ConnectionHandler;
import org.diorite.impl.connection.CoreNetworkManager;
import org.diorite.impl.connection.MinecraftEncryption;
import org.diorite.impl.connection.packets.play.clientbound.PacketPlayClientboundChat;
import org.diorite.impl.connection.packets.play.clientbound.PacketPlayClientboundPlayerListHeaderFooter;
import org.diorite.impl.connection.packets.play.clientbound.PacketPlayClientboundTitle;
import org.diorite.impl.entity.IPlayer;
import org.diorite.impl.input.ConsoleReaderThread;
import org.diorite.impl.input.InputThread;
import org.diorite.impl.log.ForwardLogHandler;
import org.diorite.impl.log.LoggerOutputStream;
import org.diorite.impl.log.TerminalConsoleWriterThread;
import org.diorite.impl.metrics.Metrics;
import org.diorite.impl.pipelines.event.chunk.ChunkGeneratePipelineImpl;
import org.diorite.impl.pipelines.event.chunk.ChunkLoadPipelineImpl;
import org.diorite.impl.pipelines.event.chunk.ChunkPopulatePipelineImpl;
import org.diorite.impl.pipelines.event.chunk.ChunkUnloadPipelineImpl;
import org.diorite.impl.pipelines.event.input.CommandPipelineImpl;
import org.diorite.impl.pipelines.event.input.TabCompletePipelineImpl;
import org.diorite.impl.pipelines.event.player.BlockDestroyPipelineImpl;
import org.diorite.impl.pipelines.event.player.BlockPlacePipelineImpl;
import org.diorite.impl.pipelines.event.player.ChatPipelineImpl;
import org.diorite.impl.pipelines.event.player.InteractPipelineImpl;
import org.diorite.impl.pipelines.event.player.InventoryClickPipelineImpl;
import org.diorite.impl.pipelines.event.player.JoinPipelineImpl;
import org.diorite.impl.pipelines.event.player.QuitPipelineImpl;
import org.diorite.impl.pipelines.system.CoreInitPipeline;
import org.diorite.impl.pipelines.system.CoreInitPipeline.InitData;
import org.diorite.impl.pipelines.system.CoreStartPipeline;
import org.diorite.impl.plugin.CoreJarPluginLoader;
import org.diorite.impl.plugin.FakePluginLoader;
import org.diorite.impl.plugin.JarPluginLoader;
import org.diorite.impl.plugin.PluginManagerImpl;
import org.diorite.impl.scheduler.SchedulerImpl;
import org.diorite.impl.world.WorldsManagerImpl;
import org.diorite.impl.world.tick.TickGroups;
import org.diorite.Core;
import org.diorite.Diorite;
import org.diorite.ItemFactory;
import org.diorite.cfg.DioriteConfig.OnlineMode;
import org.diorite.cfg.messages.DioriteMessages;
import org.diorite.cfg.system.Template;
import org.diorite.cfg.system.TemplateCreator;
import org.diorite.cfg.system.elements.BaseComponentTemplateElement;
import org.diorite.cfg.system.elements.TemplateElements;
import org.diorite.chat.ChatPosition;
import org.diorite.chat.component.BaseComponent;
import org.diorite.entity.Player;
import org.diorite.event.EventType;
import org.diorite.event.chunk.ChunkGenerateEvent;
import org.diorite.event.chunk.ChunkLoadEvent;
import org.diorite.event.chunk.ChunkPopulateEvent;
import org.diorite.event.chunk.ChunkUnloadEvent;
import org.diorite.event.input.SenderCommandEvent;
import org.diorite.event.input.SenderTabCompleteEvent;
import org.diorite.event.pipelines.event.chunk.ChunkGeneratePipeline;
import org.diorite.event.pipelines.event.chunk.ChunkLoadPipeline;
import org.diorite.event.pipelines.event.chunk.ChunkPopulatePipeline;
import org.diorite.event.pipelines.event.chunk.ChunkUnloadPipeline;
import org.diorite.event.pipelines.event.input.CommandPipeline;
import org.diorite.event.pipelines.event.input.TabCompletePipeline;
import org.diorite.event.pipelines.event.player.BlockDestroyPipeline;
import org.diorite.event.pipelines.event.player.BlockPlacePipeline;
import org.diorite.event.pipelines.event.player.ChatPipeline;
import org.diorite.event.pipelines.event.player.InteractPipeline;
import org.diorite.event.pipelines.event.player.InventoryClickPipeline;
import org.diorite.event.pipelines.event.player.JoinPipeline;
import org.diorite.event.pipelines.event.player.QuitPipeline;
import org.diorite.event.player.PlayerBlockDestroyEvent;
import org.diorite.event.player.PlayerBlockPlaceEvent;
import org.diorite.event.player.PlayerChatEvent;
import org.diorite.event.player.PlayerInteractEvent;
import org.diorite.event.player.PlayerInventoryClickEvent;
import org.diorite.event.player.PlayerJoinEvent;
import org.diorite.event.player.PlayerQuitEvent;
import org.diorite.plugin.DioritePlugin;
import org.diorite.plugin.PluginException;
import org.diorite.plugin.PluginManager;
import org.diorite.scheduler.Scheduler;
import org.diorite.scheduler.Synchronizable;
import org.diorite.utils.DioriteUtils;
import org.diorite.utils.SpammyError;
import org.diorite.utils.timings.TimingsManager;

import javassist.ClassPool;
import javassist.LoaderClassPath;
import jline.UnsupportedTerminal;
import jline.console.ConsoleReader;
import joptsimple.OptionSet;

public class DioriteCore implements Core {

    private static final CoreInitPipeline initPipeline;
    private static final CoreStartPipeline startPipeline;
    private static DioriteCore instance;

    private static final org.slf4j.Logger coreLogger = LoggerFactory.getLogger("");

    private static class SimpleSyncTask {
        private final Synchronizable sync;
        private final Runnable runnable;

        private SimpleSyncTask(final Synchronizable sync, final Runnable runnable) {
            this.sync = sync;
            this.runnable = runnable;
        }

        public Synchronizable getSynchronizable() {
            return this.sync;
        }

        public void run() {
            runSync(this.runnable);
        }

        @Override
        public String toString() {
            return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).appendSuper(super.toString())
                    .append("sync", this.sync).append("runnable", this.runnable).toString();
        }
    }

    static {
        ClassPool.getDefault().appendClassPath(new LoaderClassPath(DioriteCore.class.getClassLoader()));
        LoggerInit.init();
    }

    private static final class LoggerInit {
        private static ConsoleReader reader;
        private static Boolean coloredConsole;

        private static void init() {
            if (System.console() == null) {
                System.setProperty("jline.terminal", "jline.UnsupportedTerminal");
                CoreMain.useJline = false;
            }
            try {
                reader = new ConsoleReader(System.in, System.out);
                reader.setExpandEvents(false);
            } catch (final Throwable t) {
                t.printStackTrace();
                try {
                    System.setProperty("jline.terminal", "jline.UnsupportedTerminal");
                    System.setProperty("user.language", "en");
                    CoreMain.useJline = false;
                    reader = new ConsoleReader(System.in, System.out);
                    reader.setExpandEvents(false);
                } catch (final IOException e) {
                    coloredConsole = false;
                    e.printStackTrace();
                }
            }
            if (coloredConsole == null) {
                coloredConsole = true;
            }

            final String jline_UnsupportedTerminal = new String(new char[] { 'j', 'l', 'i', 'n', 'e', '.', 'U', 'n',
                    's', 'u', 'p', 'p', 'o', 'r', 't', 'e', 'd', 'T', 'e', 'r', 'm', 'i', 'n', 'a', 'l' });
            final String jline_terminal = new String(
                    new char[] { 'j', 'l', 'i', 'n', 'e', '.', 't', 'e', 'r', 'm', 'i', 'n', 'a', 'l' });
            CoreMain.useJline = !jline_UnsupportedTerminal.equals(System.getProperty(jline_terminal));
            if (CoreMain.useJline) {
                AnsiConsole.systemInstall();
            } else {
                System.setProperty("jline.terminal", UnsupportedTerminal.class.getName());
            }

            final java.util.logging.Logger global = java.util.logging.Logger.getLogger("");
            global.setUseParentHandlers(false);
            for (final Handler handler : global.getHandlers()) {
                global.removeHandler(handler);
            }
            global.addHandler(new ForwardLogHandler());

            final Logger logger = (Logger) LogManager.getRootLogger();
            logger.getAppenders().values().stream().filter(appender -> (appender instanceof ConsoleAppender))
                    .forEach(logger::removeAppender);

            final Thread writer = new Thread(new TerminalConsoleWriterThread(System.out));
            writer.setDaemon(true);
            writer.start();

            System.setOut(new PrintStream(new LoggerOutputStream(coreLogger, Level.INFO), true));
            System.setErr(new PrintStream(new LoggerOutputStream(coreLogger, Level.WARNING), true));
        }
    }

    {
        this.serverVersion = DioriteCore.class.getPackage().getImplementationVersion();
        if (LoggerInit.coloredConsole == null) {
            LoggerInit.init();
        }
        this.reader = LoggerInit.reader;
        this.consoleCommandSender = LoggerInit.coloredConsole ? ColoredConsoleCommandSenderImpl.getInstance(this)
                : new ConsoleCommandSenderImpl(this);
        coreLogger.info("Starting Diorite v" + this.getVersion() + " core...");
    }

    protected final boolean isClient;
    protected final CommandMapImpl commandMap = new CommandMapImpl();
    protected final TickGroups ticker = new TickGroups(this);
    protected final SchedulerImpl scheduler = new SchedulerImpl();
    protected final ConcurrentLinkedQueue<SimpleSyncTask> syncQueue = new ConcurrentLinkedQueue<>();
    protected final ItemFactory itemFactory = new ItemFactoryImpl();
    protected final Thread mainThread;
    protected final double[] recentTps = new double[3];
    private final String serverVersion;
    protected InputThread inputThread;
    protected String hostname = "127.0.0.1";
    protected int port = -1;
    protected int tps = DEFAULT_TPS;
    protected int waitTime = DEFAULT_WAIT_TIME;
    protected int connectionThrottle = 1000;
    protected double mutli = 1; // it can be used with TPS, like make 10 TPS but change this to 2, so server will scale to new TPS.
    protected YggdrasilSessionService sessionService;
    protected ConnectionHandler connectionHandler;
    protected PlayersManagerImpl playersManager;
    protected WorldsManagerImpl worldsManager;
    protected ConsoleCommandSenderImpl consoleCommandSender; //new ConsoleCommandSenderImpl(this);
    protected ConsoleReader reader;
    protected long currentTick;
    protected int keepAliveTimer;
    protected DioriteConfigImpl config;
    protected PluginManager pluginManager;
    protected TimingsManager timings;
    protected IServerManager serverManager;
    protected KeyPair keyPair = MinecraftEncryption.generateKeyPair();
    protected transient volatile boolean isRunning = true;
    protected transient volatile boolean hasStopped = false;
    protected transient volatile boolean startedCore = false;
    private Metrics metrics;

    public DioriteCore(final Proxy proxy, final OptionSet options, final boolean isClient) {
        this.isClient = isClient;
        instance = this;
        this.mainThread = Thread.currentThread();
        Diorite.setCore(this);

        this.loadConfigFile((File) options.valueOf("config"));
        if (this.config == null) {
            throw new AssertionError("Configuration instance is null after creating!");
        }
        this.loadCoreMods();
        this.startedCore = true;

        initPipeline.run(this, new InitData(options, proxy, isClient));
    }

    @Override
    public org.slf4j.Logger getLogger() {
        return coreLogger;
    }

    public boolean isStartedCore() {
        return this.startedCore;
    }

    public int getCompressionThreshold() {
        return this.config.getNetworkCompressionThreshold();
    }

    public void setCompressionThreshold(final int compressionThreshold) {
        this.config.setNetworkCompressionThreshold(compressionThreshold);
    }

    private static void runSync(final Runnable runnable) {
        try {
            runnable.run();
        } catch (final Throwable throwable) {
            throwable.printStackTrace();
        }
    }

    public void sync(final Runnable runnable, final Synchronizable sync) {
        //noinspection ObjectEquality
        if (Thread.currentThread() == sync.getLastTickThread()) {
            runSync(runnable);
            return;
        }
        this.syncQueue.add(new SimpleSyncTask(sync, runnable));
    }

    public void sync(final Runnable runnable) {
        //noinspection ObjectEquality
        if (Thread.currentThread() == this.mainThread) {
            runSync(runnable);
            return;
        }
        this.sync(runnable, this);
    }

    public void addSync(final Runnable runnable, final Synchronizable sync) {
        this.syncQueue.add(new SimpleSyncTask(sync, runnable));
    }

    public void addSync(final Runnable runnable) {
        this.addSync(runnable, this);
    }

    public void runSync() {
        for (final Iterator<SimpleSyncTask> it = this.syncQueue.iterator(); it.hasNext();) {
            final SimpleSyncTask st = it.next();
            //noinspection ObjectEquality
            if (st.getSynchronizable().getLastTickThread() == Thread.currentThread()) {
                st.run();
                it.remove();
            }
        }
    }

    @Override
    public String getVersion() {
        if (this.serverVersion == null) {
            SpammyError.err("Missing server version!", (int) TimeUnit.HOURS.toSeconds(1), "serverVersion");
            return "Unknown" + " (MC: " + Core.getMinecraftVersion() + ")";
        }
        return this.serverVersion + " (MC: " + Core.getMinecraftVersion() + ")";
    }

    @Override
    public double[] getRecentTps() {
        final double[] result = new double[this.recentTps.length];
        for (int i = 0; i < this.recentTps.length; i++) {
            result[i] = (this.recentTps[i] > this.tps) ? this.tps : this.recentTps[i];
        }
        return result;
    }

    @Override
    public ItemFactory getItemFactory() {
        return this.itemFactory;
    }

    @Override
    public List<String> getOnlinePlayersNames(final String prefix) {
        return this.playersManager.getOnlinePlayersNames(prefix);
    }

    @Override
    public List<String> getOnlinePlayersNames() {
        return this.playersManager.getOnlinePlayersNames();
    }

    @Override
    public DioriteConfigImpl getConfig() {
        return this.config;
    }

    public void setConfig(final DioriteConfigImpl config) {
        this.config = config;
    }

    @Override
    public WorldsManagerImpl getWorldsManager() {
        return this.worldsManager;
    }

    public void setWorldsManager(final WorldsManagerImpl worldsManager) {
        this.worldsManager = worldsManager;
    }

    @Override
    public Scheduler getScheduler() {
        return this.scheduler;
    }

    @Override
    public TimingsManager getTimings() {
        return this.timings;
    }

    @Override
    public void broadcastMessage(final ChatPosition position, final BaseComponent component) {
        this.playersManager.forEach(new PacketPlayClientboundChat(component, position));
        if (!Objects.equals(position, ChatPosition.ACTION)) {
            this.sendConsoleMessage(component);
        }
    }

    @Override
    public void sendConsoleMessage(final String str) {
        this.consoleCommandSender.sendMessage(str);
    }

    @Override
    public void sendConsoleMessage(final BaseComponent component) {
        this.consoleCommandSender.sendMessage(component);
    }

    @Override
    public byte getRenderDistance() {
        return (byte) this.config.getViewDistance();
    }

    @Override
    public void setRenderDistance(final byte renderDistance) {
        this.config.setViewDistance(renderDistance);
    }

    @Override
    public ConsoleCommandSenderImpl getConsoleSender() {
        return this.consoleCommandSender;
    }

    @Override
    public boolean isRunning() {
        return this.isRunning;
    }

    @Override
    public double getSpeedMutli() {
        return this.mutli;
    }

    @Override
    public void setSpeedMutli(final double mutli) {
        this.mutli = mutli;
    }

    @Override
    public int getTps() {
        return this.tps;
    }

    @Override
    public double getCpt() {
        return 100D / this.tps;
    }

    @Override
    public double getCpt(int tps) {
        return 100D / tps;
    }

    @Override
    public void resetRecentTps() {
        Arrays.fill(this.recentTps, this.tps);
    }

    @Override
    public void setTps(final int tps) {
        this.waitTime = NANOS_IN_SECOND / tps;
        this.tps = tps;
    }

    @Override
    public synchronized void stop() {
        if (!this.isRunning) {
            return;
        }
        this.isRunning = false;
        // TODO
    }

    @Override
    public void updatePlayerListHeaderAndFooter(final BaseComponent header, final BaseComponent footer) {
        this.getOnlinePlayers().forEach((player) -> this.updatePlayerListHeaderAndFooter(header, footer, player));
    }

    @Override
    public void updatePlayerListHeaderAndFooter(final BaseComponent header, final BaseComponent footer,
            final Player player) {
        ((IPlayer) player).getNetworkManager()
                .sendPacket(new PacketPlayClientboundPlayerListHeaderFooter(header, footer));
    }

    @Override
    public void broadcastTitle(final BaseComponent title, final BaseComponent subtitle, final int fadeIn,
            final int stay, final int fadeOut) {
        this.playersManager.forEach((player) -> this.sendTitle(title, subtitle, fadeIn, stay, fadeOut, player));
    }

    @Override
    public void sendTitle(final BaseComponent title, final BaseComponent subtitle, final int fadeIn, final int stay,
            final int fadeOut, final Player player) {
        final CoreNetworkManager n = ((IPlayer) player).getNetworkManager();

        if (title != null) {
            n.sendPacket(new PacketPlayClientboundTitle(PacketPlayClientboundTitle.TitleAction.SET_TITLE, title));
        }

        if (subtitle != null) {
            n.sendPacket(
                    new PacketPlayClientboundTitle(PacketPlayClientboundTitle.TitleAction.SET_SUBTITLE, subtitle));
        }

        n.sendPacket(new PacketPlayClientboundTitle(PacketPlayClientboundTitle.TitleAction.SET_TIMES, fadeIn, stay,
                fadeOut));
    }

    @Override
    public void removeTitle(final Player player) {
        ((IPlayer) player).getNetworkManager()
                .sendPacket(new PacketPlayClientboundTitle(PacketPlayClientboundTitle.TitleAction.RESET));
    }

    @Override
    public void removeAllTitles() {
        this.playersManager.forEach(new PacketPlayClientboundTitle(PacketPlayClientboundTitle.TitleAction.RESET));
    }

    @Override
    public PluginManager getPluginManager() {
        return this.pluginManager;
    }

    public void setPluginManager(final PluginManager pluginManager) {
        this.pluginManager = pluginManager;
    }

    @Override
    public CommandMapImpl getCommandMap() {
        return this.commandMap;
    }

    @Override
    public PluginCommandBuilderImpl createCommand(final DioritePlugin dioritePlugin, final String name) {
        return PluginCommandBuilderImpl.start(dioritePlugin, name);
    }

    @Override
    public Collection<Player> getOnlinePlayers() {
        return new CopyOnWriteArraySet<>(this.playersManager.getRawPlayers().values());
    }

    @Override
    public Collection<Player> getOnlinePlayers(final Predicate<Player> predicate) {
        return this.playersManager.getRawPlayers().values().stream().filter(predicate).collect(Collectors.toSet());
    }

    @Override
    public Player getPlayer(final UUID uuid) {
        return this.playersManager.getRawPlayers().get(uuid);
    }

    protected void loadConfigFile(final File f) {
        final Template<DioriteConfigImpl> cfgTemp = TemplateCreator.getTemplate(DioriteConfigImpl.class);
        if (f.exists()) {
            try {
                this.config = cfgTemp.load(f);
                if (this.config == null) {
                    this.config = cfgTemp.fillDefaults(new DioriteConfigImpl());
                }
            } catch (final IOException e) {
                throw new RuntimeException("IO exception when loading config file: " + f, e);
            }
        } else {
            this.config = cfgTemp.fillDefaults(new DioriteConfigImpl());
            try {
                DioriteUtils.createFile(f);
            } catch (final IOException e) {
                throw new RuntimeException("Can't create configuration file!", e);
            }
        }
        try {
            cfgTemp.dump(f, this.config, false);
        } catch (final IOException e) {
            throw new RuntimeException("Can't dump configuration file!", e);
        }
    }

    private void checkPluginManager() {
        if (this.pluginManager == null) {
            this.pluginManager = new PluginManagerImpl(this.config.getPluginsDirectory());
        }
        this.pluginManager.registerPluginLoader(new FakePluginLoader());
        this.pluginManager.registerPluginLoader(new JarPluginLoader());
        this.pluginManager.registerPluginLoader(new CoreJarPluginLoader());

    }

    private void loadPlugins() {
        this.checkPluginManager();
        final File dir = this.pluginManager.getDirectory();
        if (dir.exists() && !dir.isDirectory()) {
            throw new RuntimeException("Plugin directory must be a folder!");
        }
        CoreMain.debug("Plugins directory is: " + dir.getAbsolutePath());
        if (!dir.exists()) {
            dir.mkdirs();
            CoreMain.debug("Created plugins directory...");
        }

        final File[] files = dir.listFiles();
        if (files == null) {
            throw new RuntimeException("Plugin directory must be a folder!");
        }
        for (final File file : files) {
            if (file.isDirectory()) {
                continue;
            }

            try {
                this.pluginManager.loadPlugin(file);
            } catch (final PluginException e) {
                e.printStackTrace();
            }
        }

        System.out.println("Loaded " + this.pluginManager.getPlugins().size() + " plugins and core mods!");
    }

    private void loadCoreMods() {
        this.checkPluginManager();

        final File dir = this.pluginManager.getDirectory();
        if (dir.exists() && !dir.isDirectory()) {
            throw new RuntimeException("Plugin directory must be a folder!");
        }
        CoreMain.debug("Plugins directory is: " + dir.getAbsolutePath());
        if (!dir.exists()) {
            dir.mkdirs();
            CoreMain.debug("Created plugins directory...");
        }

        final File[] files = dir.listFiles();
        if (files == null) {
            throw new RuntimeException("Plugin directory must be a folder!");
        }
        for (final File file : files) {
            if (file.isDirectory()
                    || !file.getName().toLowerCase().endsWith(CoreJarPluginLoader.CORE_JAR_SUFFIX.toLowerCase())) {
                continue;
            }

            try {
                this.pluginManager.loadPlugin(file);
            } catch (final PluginException e) {
                e.printStackTrace();
            }
        }

        System.out.println("Loaded " + this.pluginManager.getPlugins().size() + " core mods!");
    }

    private void registerEvents() {
        EventType.register(SenderCommandEvent.class, CommandPipeline.class, new CommandPipelineImpl());
        EventType.register(SenderTabCompleteEvent.class, TabCompletePipeline.class, new TabCompletePipelineImpl());

        EventType.register(PlayerChatEvent.class, ChatPipeline.class, new ChatPipelineImpl());

        EventType.register(PlayerBlockDestroyEvent.class, BlockDestroyPipeline.class,
                new BlockDestroyPipelineImpl());
        EventType.register(PlayerBlockPlaceEvent.class, BlockPlacePipeline.class, new BlockPlacePipelineImpl());
        EventType.register(PlayerInventoryClickEvent.class, InventoryClickPipeline.class,
                new InventoryClickPipelineImpl());
        EventType.register(PlayerJoinEvent.class, JoinPipeline.class, new JoinPipelineImpl());
        EventType.register(PlayerQuitEvent.class, QuitPipeline.class, new QuitPipelineImpl());
        EventType.register(PlayerInteractEvent.class, InteractPipeline.class, new InteractPipelineImpl());

        EventType.register(ChunkLoadEvent.class, ChunkLoadPipeline.class, new ChunkLoadPipelineImpl());
        EventType.register(ChunkUnloadEvent.class, ChunkUnloadPipeline.class, new ChunkUnloadPipelineImpl());
        EventType.register(ChunkGenerateEvent.class, ChunkGeneratePipeline.class, new ChunkGeneratePipelineImpl());
        EventType.register(ChunkPopulateEvent.class, ChunkPopulatePipeline.class, new ChunkPopulatePipelineImpl());
    }

    public InputThread getInputThread() {
        return this.inputThread;
    }

    public void setInputThread(final InputThread inputThread) {
        this.inputThread = inputThread;
    }

    public int getKeepAliveTimer() {
        return this.keepAliveTimer;
    }

    public int getPlayerTimeout() {
        return this.config.getPlayerIdleTimeout();
    }

    public boolean isClient() {
        return this.isClient;
    }

    public String getServerVersion() {
        return this.serverVersion;
    }

    public Thread getMainThread() {
        return this.mainThread;
    }

    public long getCurrentTick() {
        return this.currentTick;
    }

    public double getMutli() {
        return this.mutli;
    }

    public synchronized void onStop() {
        if (this.hasStopped) {
            return;
        }
        PluginManagerImpl.saveCache();
        if (this.metrics != null) {
            this.metrics.stop();
        }
        if (this.pluginManager != null) {
            this.pluginManager.disablePlugins();
        }
        this.hasStopped = true;
        this.isRunning = false;
        if (this.playersManager != null) {
            this.playersManager.forEach(p -> p.kick("4Server closed!"));
        }
        if (this.connectionHandler != null) {
            this.connectionHandler.close();
        }
        if (this.worldsManager != null) {
            this.worldsManager.getWorlds().stream().forEach(w -> {
                w.save(true);
                w.getChunkManager().getService()
                        .await(i -> coreLogger.info("[" + w.getName() + "] Queue: " + i + " left."));
            });
            CoreMain.debug("done?");
        }
        System.out.println("Goodbye <3");
    }

    public KeyPair getKeyPair() {
        return this.keyPair;
    }

    public void setKeyPair(final KeyPair keyPair) {
        this.keyPair = keyPair;
    }

    public int getConnectionThrottle() {
        return this.connectionThrottle;
    }

    public void setConnectionThrottle(final int connectionThrottle) {
        this.connectionThrottle = connectionThrottle;
    }

    public ConnectionHandler getConnectionHandler() {
        return this.connectionHandler;
    }

    public void setConnectionHandler(final ConnectionHandler connectionHandler) {
        this.connectionHandler = connectionHandler;
    }

    public void setConsoleCommandSender(final ConsoleCommandSenderImpl consoleCommandSender) {
        this.consoleCommandSender = consoleCommandSender;
    }

    public PlayersManagerImpl getPlayersManager() {
        return this.playersManager;
    }

    public void setPlayersManager(final PlayersManagerImpl playersManager) {
        this.playersManager = playersManager;
    }

    @Override
    public IServerManager getServerManager() {
        return this.serverManager;
    }

    @Override
    public boolean isDebug() {
        return CoreMain.isEnabledDebug();
    }

    public void setServerManager(final IServerManager serverManager) {
        this.serverManager = serverManager;
    }

    public OnlineMode getOnlineMode() {
        return this.config.getOnlineMode();
    }

    public void setOnlineMode(final OnlineMode onlineMode) {
        this.config.setOnlineMode(onlineMode);
    }

    public int getPort() {
        return this.port;
    }

    public void setPort(final int port) {
        this.port = port;
    }

    public String getHostname() {
        return this.hostname;
    }

    public void setHostname(final String hostname) {
        this.hostname = hostname;
    }

    public Thread getMainServerThread() {
        return this.mainThread;
    }

    public String getServerModName() {
        return NAME + " v" + this.getVersion();
    }

    public ConsoleReader getReader() {
        return this.reader;
    }

    public void setReader(final ConsoleReader reader) {
        this.reader = reader;
    }

    public SessionService getSessionService() {
        return this.sessionService;
    }

    public void setSessionService(final YggdrasilSessionService sessionService) {
        this.sessionService = sessionService;
    }

    protected void start(final OptionSet options) {
        startPipeline.run(this, options);
    }

    public TickGroups getTicker() {
        return this.ticker;
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).appendSuper(super.toString()).toString();
    }

    public void runScheduler(final boolean withAsync) {
        this.scheduler.tick(this.currentTick, withAsync);
    }

    public void run() {
        PluginManagerImpl.saveCache();
        this.metrics = Metrics.start(this);
        Arrays.fill(this.recentTps, (double) DEFAULT_TPS);
        try {
            long lastTick = System.nanoTime();
            long catchupTime = 0L;
            long tickSection = lastTick;
            while (this.isRunning) {
                final long curTime = System.nanoTime();
                final long wait = this.waitTime - (curTime - lastTick) - catchupTime;
                if (wait > 0L) {
                    Thread.sleep(wait / NANOS_IN_MILLI);
                    catchupTime = 0L;
                } else {
                    catchupTime = Math.min(NANOS_IN_SECOND, Math.abs(wait));
                    if ((this.currentTick++ % 100) == 0) {
                        final double currentTps = (((double) NANOS_IN_SECOND) / (curTime - tickSection)) * 100;
                        //noinspection MagicNumber
                        this.recentTps[0] = calcTps(this.recentTps[0], 0.92D, currentTps);
                        //noinspection MagicNumber
                        this.recentTps[1] = calcTps(this.recentTps[1], 0.9835D, currentTps);
                        //noinspection MagicNumber
                        this.recentTps[2] = calcTps(this.recentTps[2], 0.9945000000000001D, currentTps);
                        tickSection = curTime;
                    }
                    lastTick = curTime;

                    this.runScheduler(true);

                    this.runSync();

                    this.playersManager.doTick(this.tps);
                    this.ticker.doTick(this.tps);
                }
            }
        } catch (final Throwable e) {
            e.printStackTrace();
            this.onStop();
            return;
        }
        CoreMain.debug("Main loop finished, stopping server. (" + this.hasStopped + ")");
        if (!this.hasStopped) {
            this.onStop();
        }
    }

    @Override
    public Thread getLastTickThread() {
        return this.mainThread;
    }

    @Override
    public boolean isValidSynchronizable() {
        return this.isRunning;
    }

    public static CoreInitPipeline getInitPipeline() {
        return initPipeline;
    }

    public static CoreStartPipeline getStartPipeline() {
        return startPipeline;
    }

    private static double calcTps(final double avg, final double exp, final double tps) {
        return (avg * exp) + (tps * (1.0D - exp));
    }

    public static DioriteCore getInstance() {
        return instance;
    }

    static {
        Reflections.log = null;
        initPipeline = new CoreInitPipeline();
        initPipeline.addLast("DioriteCore|LoadBasicSettings", (s, p, d) -> {
            s.keepAliveTimer = (int) d.options.valueOf("keepalivetimer");

            if (d.options.has("online")) {
                final OnlineMode mode = OnlineMode.valueOf(d.options.valueOf("online").toString().toUpperCase());
                if (mode != null) {
                    s.config.setOnlineMode(mode);
                }
            }
        });
        initPipeline.addLast("DioriteCore|registerTemplateElements",
                (s, p, d) -> TemplateElements.getElements().addAfter(Locale.class.getName(),
                        BaseComponent.class.getName(), BaseComponentTemplateElement.INSTANCE));
        initPipeline.addLast("DioriteCore|registerEvents", (s, p, d) -> s.registerEvents());
        initPipeline.addLast("DioriteCore|initTimings", (s, p, d) -> s.timings = new TimingsManagerImpl());
        initPipeline.addLast("DioriteCore|initSessionService",
                (s, p, d) -> s.sessionService = new YggdrasilSessionService(d.proxy, UUID.randomUUID().toString()));
        initPipeline.addLast("DioriteCore|addShutdownHook",
                (s, p, d) -> Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                    try {
                        s.onStop();
                    } catch (final Exception e) {
                        e.printStackTrace();
                    }
                })));
        initPipeline.addLast("DioriteCore|RegisterDefaultCommands",
                (s, p, d) -> RegisterDefaultCommands.init(s.commandMap));
        initPipeline.addLast("DioriteCore|initInputThread",
                (s, p, d) -> s.inputThread = InputThread.start(s.config.getInputThreadPoolSize()));
        initPipeline.addLast("DioriteCore|initGame", (s, p, d) -> {
            s.serverManager = new ServerManagerImpl(s);
            s.playersManager = new PlayersManagerImpl(s);
            s.worldsManager = new WorldsManagerImpl(s);

            DioriteMessages.reload();
        });
        initPipeline.addLast("DioriteCore|initRecipes",
                (s, p, d) -> s.serverManager.getRecipeManager().addDefaultRecipes());

        startPipeline = new CoreStartPipeline();
        startPipeline.addLast("DioriteCore|EnableMods", (s, pipeline, options) -> s.getPluginManager().getPlugins()
                .stream().filter(p -> !p.isEnabled() && p.isCoreMod()).forEach(p -> p.setEnabled(true)));
        startPipeline.addLast("DioriteCore|LoadPlugins", (s, p, options) -> s.loadPlugins());

        startPipeline.addLast("DioriteCore|EnablePlugins", (s, pipeline, options) -> s.pluginManager.getPlugins()
                .stream().filter(p -> !p.isEnabled()).forEach(p -> p.setEnabled(true)));
        // TODO configuration and other shit.
        startPipeline.addLast("DioriteCore|ConsoleReaderThreadStart",
                (s, p, options) -> ConsoleReaderThread.start(s));
    }
}