de.minigameslib.mclib.impl.MclibPlugin.java Source code

Java tutorial

Introduction

Here is the source code for de.minigameslib.mclib.impl.MclibPlugin.java

Source

/*
Copyright 2016 by minigameslib.de
All rights reserved.
If you do not own a hand-signed commercial license from minigames.de
you are not allowed to use this software in any way except using
GPL (see below).
    
------
    
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
*/

package de.minigameslib.mclib.impl;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.stream.Collectors;

import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.event.server.PluginEnableEvent;
import org.bukkit.event.world.WorldLoadEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.ServicePriority;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.plugin.messaging.PluginMessageListener;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scheduler.BukkitTask;

import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;

import de.minigames.mclib.nms.v18.NetworkManager1_8;
import de.minigames.mclib.nms.v18.NmsFactory1_8;
import de.minigames.mclib.nms.v183.NmsFactory1_8_3;
import de.minigames.mclib.nms.v185.NmsFactory1_8_5;
import de.minigames.mclib.nms.v19.NmsFactory1_9;
import de.minigames.mclib.nms.v194.NmsFactory1_9_4;
import de.minigameslib.mclib.api.CommonMessages;
import de.minigameslib.mclib.api.EditableValue;
import de.minigameslib.mclib.api.McContext;
import de.minigameslib.mclib.api.McException;
import de.minigameslib.mclib.api.McLibInterface;
import de.minigameslib.mclib.api.MinecraftVersionsType;
import de.minigameslib.mclib.api.bungee.BungeeServerInterface;
import de.minigameslib.mclib.api.bungee.BungeeServiceInterface;
import de.minigameslib.mclib.api.cmd.CommandImpl;
import de.minigameslib.mclib.api.cmd.CommandInterface;
import de.minigameslib.mclib.api.com.CommunicationBungeeHandler;
import de.minigameslib.mclib.api.com.CommunicationPeerHandler;
import de.minigameslib.mclib.api.com.ServerCommunicationServiceInterface;
import de.minigameslib.mclib.api.config.ConfigColorData;
import de.minigameslib.mclib.api.config.ConfigInterface;
import de.minigameslib.mclib.api.config.ConfigItemStackData;
import de.minigameslib.mclib.api.config.ConfigServiceInterface;
import de.minigameslib.mclib.api.config.ConfigVectorData;
import de.minigameslib.mclib.api.config.ConfigurationValueInterface;
import de.minigameslib.mclib.api.enums.EnumServiceInterface;
import de.minigameslib.mclib.api.event.McListener;
import de.minigameslib.mclib.api.event.MinecraftEvent;
import de.minigameslib.mclib.api.ext.ExtensionInterface;
import de.minigameslib.mclib.api.ext.ExtensionPointInterface;
import de.minigameslib.mclib.api.ext.ExtensionServiceInterface;
import de.minigameslib.mclib.api.gui.ClickGuiItem;
import de.minigameslib.mclib.api.gui.ClickGuiItem.GuiItemHandler;
import de.minigameslib.mclib.api.gui.GuiServiceInterface;
import de.minigameslib.mclib.api.gui.RawMessageInterface;
import de.minigameslib.mclib.api.gui.SimpleClickGui;
import de.minigameslib.mclib.api.items.BlockServiceInterface;
import de.minigameslib.mclib.api.items.CommonItems;
import de.minigameslib.mclib.api.items.InventoryId;
import de.minigameslib.mclib.api.items.InventoryServiceInterface;
import de.minigameslib.mclib.api.items.ItemServiceInterface;
import de.minigameslib.mclib.api.items.ResourceServiceInterface;
import de.minigameslib.mclib.api.locale.LocalizedConfigLine;
import de.minigameslib.mclib.api.locale.LocalizedConfigString;
import de.minigameslib.mclib.api.locale.LocalizedMessageInterface;
import de.minigameslib.mclib.api.locale.MessageServiceInterface;
import de.minigameslib.mclib.api.locale.MessagesConfigInterface;
import de.minigameslib.mclib.api.mcevent.ComponentCreateEvent;
import de.minigameslib.mclib.api.mcevent.ComponentCreatedEvent;
import de.minigameslib.mclib.api.mcevent.ComponentDeleteEvent;
import de.minigameslib.mclib.api.mcevent.ComponentDeletedEvent;
import de.minigameslib.mclib.api.mcevent.ComponentRelocateEvent;
import de.minigameslib.mclib.api.mcevent.ComponentRelocatedEvent;
import de.minigameslib.mclib.api.mcevent.EntityCreateEvent;
import de.minigameslib.mclib.api.mcevent.EntityCreatedEvent;
import de.minigameslib.mclib.api.mcevent.EntityDeleteEvent;
import de.minigameslib.mclib.api.mcevent.EntityDeletedEvent;
import de.minigameslib.mclib.api.mcevent.EntityEnteredZoneEvent;
import de.minigameslib.mclib.api.mcevent.EntityLeftZoneEvent;
import de.minigameslib.mclib.api.mcevent.ObjectCreateEvent;
import de.minigameslib.mclib.api.mcevent.ObjectCreatedEvent;
import de.minigameslib.mclib.api.mcevent.ObjectDeleteEvent;
import de.minigameslib.mclib.api.mcevent.ObjectDeletedEvent;
import de.minigameslib.mclib.api.mcevent.PlayerCloseGuiEvent;
import de.minigameslib.mclib.api.mcevent.PlayerDisplayGuiPageEvent;
import de.minigameslib.mclib.api.mcevent.PlayerEnteredZoneEvent;
import de.minigameslib.mclib.api.mcevent.PlayerGuiClickEvent;
import de.minigameslib.mclib.api.mcevent.PlayerLeftZoneEvent;
import de.minigameslib.mclib.api.mcevent.PlayerOpenGuiEvent;
import de.minigameslib.mclib.api.mcevent.SignCreateEvent;
import de.minigameslib.mclib.api.mcevent.SignCreatedEvent;
import de.minigameslib.mclib.api.mcevent.SignDeleteEvent;
import de.minigameslib.mclib.api.mcevent.SignDeletedEvent;
import de.minigameslib.mclib.api.mcevent.SignRelocateEvent;
import de.minigameslib.mclib.api.mcevent.SignRelocatedEvent;
import de.minigameslib.mclib.api.mcevent.ZoneCreateEvent;
import de.minigameslib.mclib.api.mcevent.ZoneCreatedEvent;
import de.minigameslib.mclib.api.mcevent.ZoneDeleteEvent;
import de.minigameslib.mclib.api.mcevent.ZoneDeletedEvent;
import de.minigameslib.mclib.api.mcevent.ZoneRelocateEvent;
import de.minigameslib.mclib.api.mcevent.ZoneRelocatedEvent;
import de.minigameslib.mclib.api.objects.ComponentIdInterface;
import de.minigameslib.mclib.api.objects.ComponentInterface;
import de.minigameslib.mclib.api.objects.EntityIdInterface;
import de.minigameslib.mclib.api.objects.HologramIdInterface;
import de.minigameslib.mclib.api.objects.McPlayerInterface;
import de.minigameslib.mclib.api.objects.NpcServiceInterface;
import de.minigameslib.mclib.api.objects.ObjectIdInterface;
import de.minigameslib.mclib.api.objects.ObjectServiceInterface;
import de.minigameslib.mclib.api.objects.SignIdInterface;
import de.minigameslib.mclib.api.objects.ZoneIdInterface;
import de.minigameslib.mclib.api.objects.ZoneInterface;
import de.minigameslib.mclib.api.perms.PermissionServiceInterface;
import de.minigameslib.mclib.api.preg.PluginObjectRegistry;
import de.minigameslib.mclib.api.schem.SchemataServiceInterface;
import de.minigameslib.mclib.api.skin.SkinServiceInterface;
import de.minigameslib.mclib.api.util.function.McConsumer;
import de.minigameslib.mclib.api.util.function.McRunnable;
import de.minigameslib.mclib.api.util.function.McSupplier;
import de.minigameslib.mclib.impl.cmd.MclibCommand;
import de.minigameslib.mclib.impl.com.PlayerProxy;
import de.minigameslib.mclib.impl.comp.ComponentId;
import de.minigameslib.mclib.impl.comp.EntityId;
import de.minigameslib.mclib.impl.comp.HologramId;
import de.minigameslib.mclib.impl.comp.ObjectId;
import de.minigameslib.mclib.impl.comp.SignId;
import de.minigameslib.mclib.impl.comp.ZoneId;
import de.minigameslib.mclib.impl.gui.ClickGuis;
import de.minigameslib.mclib.impl.gui.GuiServiceImpl;
import de.minigameslib.mclib.impl.gui.cfg.AbstractConfigOption;
import de.minigameslib.mclib.impl.gui.etc.LocalizedLinesList;
import de.minigameslib.mclib.impl.gui.etc.LocalizedStringList;
import de.minigameslib.mclib.impl.items.ConfigItemStackWrapper;
import de.minigameslib.mclib.impl.items.InventoryIdImpl;
import de.minigameslib.mclib.impl.items.InventoryListener;
import de.minigameslib.mclib.impl.items.InventoryServiceImpl;
import de.minigameslib.mclib.impl.items.ItemServiceImpl;
import de.minigameslib.mclib.impl.items.McInventoriesConfig;
import de.minigameslib.mclib.impl.obj.ObjectsManager;
import de.minigameslib.mclib.impl.schem.SchemataServiceImpl;
import de.minigameslib.mclib.impl.skin.SkinServiceImpl;
import de.minigameslib.mclib.impl.yml.YmlFile;
import de.minigameslib.mclib.nms.api.AnvilManagerInterface;
import de.minigameslib.mclib.nms.api.EntityHelperInterface;
import de.minigameslib.mclib.nms.api.EventBus;
import de.minigameslib.mclib.nms.api.EventSystemInterface;
import de.minigameslib.mclib.nms.api.HologramHelperInterface;
import de.minigameslib.mclib.nms.api.InventoryManagerInterface;
import de.minigameslib.mclib.nms.api.ItemHelperInterface;
import de.minigameslib.mclib.nms.api.MgEventListener;
import de.minigameslib.mclib.nms.api.NmsFactory;
import de.minigameslib.mclib.nms.api.PlayerManagerInterface;
import de.minigameslib.mclib.nms.api.SignHelperInterface;
import de.minigameslib.mclib.nms.v110.NmsFactory1_10_1;
import de.minigameslib.mclib.nms.v111.NmsFactory1_11;
import de.minigameslib.mclib.nms.v112.NmsFactory1_12;
import de.minigameslib.mclib.pshared.ActionPerformedData;
import de.minigameslib.mclib.pshared.CoreMessages;
import de.minigameslib.mclib.pshared.MclibCommunication;
import de.minigameslib.mclib.pshared.PingData;
import de.minigameslib.mclib.pshared.PongData;
import de.minigameslib.mclib.pshared.QueryFormRequestData;
import de.minigameslib.mclib.pshared.WinClosedData;
import de.minigameslib.mclib.shared.api.com.ColorDataFragment;
import de.minigameslib.mclib.shared.api.com.CommunicationEndpointId;
import de.minigameslib.mclib.shared.api.com.DataSection;
import de.minigameslib.mclib.shared.api.com.ItemStackDataFragment;
import de.minigameslib.mclib.shared.api.com.MemoryDataSection;
import de.minigameslib.mclib.shared.api.com.PlayerDataFragment;
import de.minigameslib.mclib.shared.api.com.UniqueEnumerationValue;
import de.minigameslib.mclib.shared.api.com.VectorDataFragment;

/**
 * Main spigot plugin class for MCLIB.
 * 
 * @author mepeisen
 */
public class MclibPlugin extends JavaPlugin implements Listener, ConfigServiceInterface, MessageServiceInterface,
        PermissionServiceInterface, McLibInterface, ServerCommunicationServiceInterface, ExtensionServiceInterface,
        PluginMessageListener, BungeeServiceInterface, MgEventListener {

    /**
     * plugin channel for messages between servers and clients.
     */
    private static final String MCLIB_SERVER_TO_CLIENT_CHANNEL = "mclib|sc"; //$NON-NLS-1$

    /**
     * plugin channel for messages between bungee servers.
     */
    private static final String MCLIB_SERVER_TO_SERVER_CHANNEL = "mclib|bc"; //$NON-NLS-1$

    /**
     * plugin channel for BungeeCord.
     */
    private static final String BUNGEECORD_CHANNEL = "BungeeCord"; //$NON-NLS-1$

    /** the overall minecraft server versioon. */
    private static final MinecraftVersionsType SERVER_VERSION = MclibPlugin.getServerVersion();

    /** context helper. */
    private final McContextImpl context = new McContextImpl();

    /** enum service helper. */
    private final EnumServiceImpl enumService = new EnumServiceImpl();

    /** message service helper. */
    private MessageServiceInterface msgService = new MessageServiceImpl(this.enumService);

    /**
     * configuration per plugin.
     */
    private final Map<Plugin, ConfigInterface> configurations = new HashMap<>();

    /**
     * configuration per plugin.
     */
    private final Map<Plugin, Map<File, ConfigInterface>> configurationsPerDataFolder = new HashMap<>();

    /**
     * configuration providers per plugin.
     */
    private final Map<Plugin, Map<Class<?>, McSupplier<File>>> configProviders = new HashMap<>();

    /** the player registry. */
    PlayerRegistry players;

    /** the objects manager. */
    ObjectsManager objectsManager;

    /** the known endpoints. */
    private final Map<String, Map<String, CommunicationEndpointId>> peerEndpoints = new HashMap<>();

    /** the known handlers. */
    private final Map<CommunicationEndpointId, CommunicationPeerHandler> peerHandlers = new ConcurrentHashMap<>();

    /** the known handlers. */
    private final Map<String, List<CommunicationEndpointId>> peerEndpointPlugins = new ConcurrentHashMap<>();

    /** the known endpoints. */
    private final Map<String, Map<String, CommunicationEndpointId>> bungeeEndpoints = new HashMap<>();

    /** the known handlers. */
    final Map<CommunicationEndpointId, CommunicationBungeeHandler> bungeeHandlers = new ConcurrentHashMap<>();

    /** the known handlers. */
    private final Map<String, List<CommunicationEndpointId>> bungeeEndpointPlugins = new ConcurrentHashMap<>();

    /** type of endpoints: true is a client endpoint, false a server endpoint. */
    private final Map<CommunicationEndpointId, Boolean> endpointTypes = new HashMap<>();

    /** the current bungee server (myself). */
    private final LocalBungeeServer currentBungee = new LocalBungeeServer();

    /** the known other bungee servers from network. */
    private final Map<String, BungeeServerInterface> bungeeServers = new HashMap<>();

    /**
     * queue for bungee messages.
     */
    final Queue<Consumer<Player>> bungeeQueue = new ConcurrentLinkedQueue<>();

    /**
     * the get servers ping.
     */
    private GetServersPing serversPing;

    /** the event bus. */
    private EventBus eventBus;

    /** mclib chat command. */
    private MclibCommand mclibCommand = new MclibCommand();

    /**
     * plugin instance.
     */
    private static MclibPlugin instance;

    /**
     * asynchronous thread pools.
     */
    private final ExecutorService executor = Executors.newFixedThreadPool(3); // TODO configure threads

    /**
     * the item service impl.
     */
    ItemServiceImpl itemService;

    /** registered extensions. */
    private final PluginObjectRegistry<ExtensionPointInterface<?>, ExtensionInterface> extensions = new PluginObjectRegistry<>();

    static {
        if (MemoryDataSection.isFragmentImplementationLocked()) {
            throw new IllegalStateException("Invalid class initialization"); //$NON-NLS-1$
        }
        MemoryDataSection.initFragmentImplementation(PlayerDataFragment.class, PlayerProxy.class);
        MemoryDataSection.initFragmentImplementation(McPlayerInterface.class, PlayerProxy.class);
        MemoryDataSection.initFragmentImplementation(ColorDataFragment.class, ConfigColorData.class);
        MemoryDataSection.initFragmentImplementation(ItemStackDataFragment.class, ConfigItemStackWrapper.class);
        MemoryDataSection.initFragmentImplementation(ConfigItemStackData.class, ConfigItemStackWrapper.class);
        MemoryDataSection.initFragmentImplementation(VectorDataFragment.class, ConfigVectorData.class);

        MemoryDataSection.initFragmentImplementation(ObjectIdInterface.class, ObjectId.class);
        MemoryDataSection.initFragmentImplementation(ComponentIdInterface.class, ComponentId.class);
        MemoryDataSection.initFragmentImplementation(ZoneIdInterface.class, ZoneId.class);
        MemoryDataSection.initFragmentImplementation(SignIdInterface.class, SignId.class);
        MemoryDataSection.initFragmentImplementation(EntityIdInterface.class, EntityId.class);
        MemoryDataSection.initFragmentImplementation(HologramIdInterface.class, HologramId.class);

        MemoryDataSection.initFragmentImplementation(InventoryId.class, InventoryIdImpl.class);

        MemoryDataSection.initUniqueEnumValueFactory(MclibPlugin::create);
        MemoryDataSection.lockFragmentImplementations();
    }

    @Override
    public int getApiVersion() {
        return APIVERSION_1_0_0;
    }

    @Override
    public void onEnable() {
        instance = this;

        // detect server version
        detectServerVersionAndInstallNms();

        Bukkit.getPluginManager().registerEvents(this, this);

        // mclib enumerations
        this.enumService.registerEnumClass(this, CommonMessages.class);
        this.enumService.registerEnumClass(this, MclibCommand.Messages.class);
        this.enumService.registerEnumClass(this, InventoryServiceImpl.Messages.class);
        this.enumService.registerEnumClass(this, AbstractConfigOption.Messages.class);
        this.enumService.registerEnumClass(this, MclibCommand.CommandPermissions.class);
        this.enumService.registerEnumClass(this, McCoreConfig.class);
        this.enumService.registerEnumClass(this, McModdedWorldConfig.class);
        this.enumService.registerEnumClass(this, McInventoriesConfig.class);

        // public api services
        registerPublicServices();

        this.players = new PlayerRegistry(new File(this.getDataFolder(), "players")); //$NON-NLS-1$

        // item service
        initItemsAndBlocksAndResources();

        Bukkit.getServicesManager().register(InventoryServiceInterface.class,
                new InventoryServiceImpl(new File(this.getDataFolder(), "inventories")), this, //$NON-NLS-1$
                ServicePriority.Highest);

        CommunicationEndpointId.CommunicationServiceCache.init(this);

        // nms services
        initNms();

        registerMclibEvents();

        initObjectsManager();

        // nms event listeners
        Bukkit.getPluginManager().registerEvents(Bukkit.getServicesManager().load(EventSystemInterface.class),
                this);
        Bukkit.getPluginManager().registerEvents(Bukkit.getServicesManager().load(InventoryManagerInterface.class),
                this);
        Bukkit.getPluginManager().registerEvents(Bukkit.getServicesManager().load(AnvilManagerInterface.class),
                this);

        // communication endpoints
        initNetworking();

        if (this.getMinecraftVersion().isAtLeast(MinecraftVersionsType.V1_8_R3)) {
            Bukkit.getPluginManager().registerEvents(new ResourcePackListener(this.players, this.itemService),
                    this);
        }
        this.itemService.init();
    }

    /**
     * Initialize networking stuff.
     */
    private void initNetworking() {
        this.registerPeerHandler(this, MclibCommunication.ClientServerCore, new MclibCoreHandler());

        // network
        // sc = s[erver]c[client] (both directions)
        Bukkit.getMessenger().registerOutgoingPluginChannel(this, MCLIB_SERVER_TO_CLIENT_CHANNEL);
        Bukkit.getMessenger().registerIncomingPluginChannel(this, MCLIB_SERVER_TO_CLIENT_CHANNEL, this);
        // bc = b[ungee]c[oord]
        Bukkit.getMessenger().registerOutgoingPluginChannel(this, MCLIB_SERVER_TO_SERVER_CHANNEL);
        Bukkit.getMessenger().registerIncomingPluginChannel(this, MCLIB_SERVER_TO_SERVER_CHANNEL, this);
        // bungeecord
        Bukkit.getMessenger().registerOutgoingPluginChannel(this, BUNGEECORD_CHANNEL);
        Bukkit.getMessenger().registerIncomingPluginChannel(this, BUNGEECORD_CHANNEL, this);

        final ByteArrayDataOutput out = ByteStreams.newDataOutput();
        out.writeUTF("GetServer"); //$NON-NLS-1$
        this.bungeeQueue.add(p -> p.sendPluginMessage(this, BUNGEECORD_CHANNEL, out.toByteArray()));
        final ByteArrayDataOutput out2 = ByteStreams.newDataOutput();
        out2.writeUTF("GetServers"); //$NON-NLS-1$
        this.bungeeQueue.add(p -> p.sendPluginMessage(this, BUNGEECORD_CHANNEL, out2.toByteArray()));

        this.serversPing = new GetServersPing();
        this.serversPing.runTaskTimer(this, 20 * 5, 20 * 60); // once per minute
    }

    /**
     * Initialize the objects manager.
     */
    private void initObjectsManager() {
        try {
            this.objectsManager = new ObjectsManager(this.getDataFolder(), this.players);
            Bukkit.getServicesManager().register(ObjectServiceInterface.class, this.objectsManager, this,
                    ServicePriority.Highest);
            Bukkit.getServicesManager().register(NpcServiceInterface.class, this.objectsManager, this,
                    ServicePriority.Highest);
        } catch (McException ex) {
            this.getLogger().log(Level.SEVERE, "Problems creating objects manager", ex); //$NON-NLS-1$
            // TODO what do we do at this point?
            // having no object manager will cause a dead plugin at all
        }
        new BukkitRunnable() {

            @Override
            public void run() {
                MclibPlugin.this.objectsManager.init();
            }
        }.runTaskLater(this, 1);
    }

    /**
     * Initialize nms related classes.
     */
    private void initNms() {
        final NmsFactory factory = Bukkit.getServicesManager().load(NmsFactory.class);
        factory.create(ItemHelperInterface.class).initBlocks();
        factory.create(ItemHelperInterface.class).initItems();
        Bukkit.getServicesManager().register(EventSystemInterface.class, factory.create(EventSystemInterface.class),
                this, ServicePriority.Highest);
        Bukkit.getServicesManager().register(InventoryManagerInterface.class,
                factory.create(InventoryManagerInterface.class), this, ServicePriority.Highest);
        Bukkit.getServicesManager().register(AnvilManagerInterface.class,
                factory.create(AnvilManagerInterface.class), this, ServicePriority.Highest);

        Bukkit.getServicesManager().load(EventSystemInterface.class).addEventListener(this);
        this.eventBus = Bukkit.getServicesManager().load(EventSystemInterface.class).createEventBus();
    }

    /**
     * Initialize mclib specific events.
     */
    private void registerMclibEvents() {
        this.registerEvent(this, ComponentCreatedEvent.class);
        this.registerEvent(this, ComponentCreateEvent.class);
        this.registerEvent(this, ComponentDeletedEvent.class);
        this.registerEvent(this, ComponentDeleteEvent.class);
        this.registerEvent(this, ComponentRelocatedEvent.class);
        this.registerEvent(this, ComponentRelocateEvent.class);
        this.registerEvent(this, EntityCreatedEvent.class);
        this.registerEvent(this, EntityCreateEvent.class);
        this.registerEvent(this, EntityDeletedEvent.class);
        this.registerEvent(this, EntityDeleteEvent.class);
        this.registerEvent(this, EntityEnteredZoneEvent.class);
        this.registerEvent(this, EntityLeftZoneEvent.class);
        this.registerEvent(this, ObjectCreatedEvent.class);
        this.registerEvent(this, ObjectCreateEvent.class);
        this.registerEvent(this, ObjectDeletedEvent.class);
        this.registerEvent(this, ObjectDeleteEvent.class);
        this.registerEvent(this, PlayerCloseGuiEvent.class);
        this.registerEvent(this, PlayerDisplayGuiPageEvent.class);
        this.registerEvent(this, PlayerEnteredZoneEvent.class);
        this.registerEvent(this, PlayerGuiClickEvent.class);
        this.registerEvent(this, PlayerLeftZoneEvent.class);
        this.registerEvent(this, PlayerOpenGuiEvent.class);
        this.registerEvent(this, SignCreatedEvent.class);
        this.registerEvent(this, SignCreateEvent.class);
        this.registerEvent(this, SignDeletedEvent.class);
        this.registerEvent(this, SignDeleteEvent.class);
        this.registerEvent(this, SignRelocatedEvent.class);
        this.registerEvent(this, SignRelocateEvent.class);
        this.registerEvent(this, ZoneCreatedEvent.class);
        this.registerEvent(this, ZoneCreateEvent.class);
        this.registerEvent(this, ZoneDeletedEvent.class);
        this.registerEvent(this, ZoneDeleteEvent.class);
        this.registerEvent(this, ZoneRelocatedEvent.class);
        this.registerEvent(this, ZoneRelocateEvent.class);
    }

    /**
     * Initialize modded world support (custom blocks and items).
     */
    private void initItemsAndBlocksAndResources() {
        this.enumService.registerEnumClass(this, CommonItems.class);
        this.itemService = new ItemServiceImpl();
        Bukkit.getServicesManager().register(ItemServiceInterface.class, this.itemService, this,
                ServicePriority.Highest);
        Bukkit.getServicesManager().register(ResourceServiceInterface.class, this.itemService, this,
                ServicePriority.Highest);
        Bukkit.getServicesManager().register(BlockServiceInterface.class, this.itemService, this,
                ServicePriority.Highest);
        new BukkitRunnable() {

            @Override
            public void run() {
                try {
                    MclibPlugin.this.itemService.createResourcePack(
                            new File(MclibPlugin.this.getDataFolder(), "mclib_core_resources_v1.zip"), //$NON-NLS-1$
                            ResourceServiceInterface.ResourceVersion.PACK_FORMAT_1);
                    MclibPlugin.this.itemService.createResourcePack(
                            new File(MclibPlugin.this.getDataFolder(), "mclib_core_resources_v2.zip"), //$NON-NLS-1$
                            ResourceServiceInterface.ResourceVersion.PACK_FORMAT_2);
                    MclibPlugin.this.itemService.createResourcePack(
                            new File(MclibPlugin.this.getDataFolder(), "mclib_core_resources_v3.zip"), //$NON-NLS-1$
                            ResourceServiceInterface.ResourceVersion.PACK_FORMAT_3);
                } catch (IOException e) {
                    MclibPlugin.this.getLogger().log(Level.WARNING, "Error creating resource pack", e); //$NON-NLS-1$
                }
            }
        }.runTaskLaterAsynchronously(this, 2);
    }

    /**
     * Register some public services for mclib api support.
     */
    private void registerPublicServices() {
        Bukkit.getServicesManager().register(EnumServiceInterface.class, this.enumService, this,
                ServicePriority.Highest);
        Bukkit.getServicesManager().register(ConfigServiceInterface.class, this, this, ServicePriority.Highest);
        Bukkit.getServicesManager().register(MessageServiceInterface.class, this, this, ServicePriority.Highest);
        Bukkit.getServicesManager().register(PermissionServiceInterface.class, this, this, ServicePriority.Highest);
        Bukkit.getServicesManager().register(McContext.class, this, this, ServicePriority.Highest);
        Bukkit.getServicesManager().register(McLibInterface.class, this, this, ServicePriority.Highest);
        Bukkit.getServicesManager().register(ServerCommunicationServiceInterface.class, this, this,
                ServicePriority.Highest);
        Bukkit.getServicesManager().register(ExtensionServiceInterface.class, this, this, ServicePriority.Highest);
        Bukkit.getServicesManager().register(BungeeServiceInterface.class, this, this, ServicePriority.Highest);
        Bukkit.getServicesManager().register(GuiServiceInterface.class, new GuiServiceImpl(), this,
                ServicePriority.Highest);

        Bukkit.getServicesManager().register(SchemataServiceInterface.class, new SchemataServiceImpl(this.executor),
                this, ServicePriority.Highest);
        Bukkit.getServicesManager().register(SkinServiceInterface.class, new SkinServiceImpl(this.executor), this,
                ServicePriority.Highest);
    }

    /**
     * Detect spigot version and setup nms factory.
     */
    private void detectServerVersionAndInstallNms() {
        switch (SERVER_VERSION) {
        case Unknown:
        case V1_7:
        case V1_7_R1:
        case V1_7_R2:
        case V1_7_R3:
        case V1_7_R4:
        default:
            throw new IllegalStateException("Unsupported minecraft server version."); //$NON-NLS-1$
        case V1_12:
        case V1_12_R1:
            Bukkit.getServicesManager().register(NmsFactory.class, new NmsFactory1_12(), this,
                    ServicePriority.Highest);
            break;
        case V1_11:
        case V1_11_R1:
            Bukkit.getServicesManager().register(NmsFactory.class, new NmsFactory1_11(), this,
                    ServicePriority.Highest);
            break;
        case V1_10:
        case V1_10_R1:
            Bukkit.getServicesManager().register(NmsFactory.class, new NmsFactory1_10_1(), this,
                    ServicePriority.Highest);
            break;
        case V1_8:
        case V1_8_R1:
            Bukkit.getServicesManager().register(NmsFactory.class, new NmsFactory1_8(), this,
                    ServicePriority.Highest);
            break;
        case V1_8_R2:
            Bukkit.getServicesManager().register(NmsFactory.class, new NmsFactory1_8_3(), this,
                    ServicePriority.Highest);
            break;
        case V1_8_R3:
            Bukkit.getServicesManager().register(NmsFactory.class, new NmsFactory1_8_5(), this,
                    ServicePriority.Highest);
            break;
        case V1_9:
        case V1_9_R1:
            Bukkit.getServicesManager().register(NmsFactory.class, new NmsFactory1_9(), this,
                    ServicePriority.Highest);
            break;
        case V1_9_R2:
            Bukkit.getServicesManager().register(NmsFactory.class, new NmsFactory1_9_4(), this,
                    ServicePriority.Highest);
            break;
        }
    }

    /**
     * The get servers ping.
     * 
     * @author mepeisen
     */
    private final class GetServersPing extends BukkitRunnable {

        /**
         * Constructor.
         */
        public GetServersPing() {
            // empty.
        }

        @Override
        public void run() {
            final ObjectServiceInterface osi = ObjectServiceInterface.instance();
            final Optional<? extends Player> player = Bukkit.getOnlinePlayers().stream()
                    .filter(p -> !osi.isHuman(p)).findFirst();
            if (player.isPresent()) {
                // bungee cord ensures this is not send to the client.
                final ByteArrayDataOutput out2 = ByteStreams.newDataOutput();
                out2.writeUTF("GetServers"); //$NON-NLS-1$
                player.get().sendPluginMessage(MclibPlugin.this, BUNGEECORD_CHANNEL, out2.toByteArray());
            }
        }

    }

    @Override
    public void onDisable() {
        this.objectsManager.disable();
        this.enumService.unregisterAllEnumerations(this);
        this.removeAllCommunicationEndpoints(this);
        Bukkit.getServicesManager().unregisterAll(this);

        this.serversPing.cancel();
        this.serversPing = null;

        Bukkit.getMessenger().unregisterOutgoingPluginChannel(this, MCLIB_SERVER_TO_CLIENT_CHANNEL);
        Bukkit.getMessenger().unregisterIncomingPluginChannel(this, MCLIB_SERVER_TO_CLIENT_CHANNEL, this);
        Bukkit.getMessenger().unregisterOutgoingPluginChannel(this, MCLIB_SERVER_TO_SERVER_CHANNEL);
        Bukkit.getMessenger().unregisterIncomingPluginChannel(this, MCLIB_SERVER_TO_SERVER_CHANNEL, this);
        Bukkit.getMessenger().unregisterOutgoingPluginChannel(this, BUNGEECORD_CHANNEL);
        Bukkit.getMessenger().unregisterIncomingPluginChannel(this, BUNGEECORD_CHANNEL, this);
    }

    /**
     * World loaded.
     * 
     * @param evt
     *            event
     */
    @EventHandler
    public void onWorldLoaded(WorldLoadEvent evt) {
        this.objectsManager.onWorldLoaded(evt.getWorld());
    }

    /**
     * Calculates the minecraft server version.
     * 
     * @return Minecraft server version.
     */
    private static MinecraftVersionsType getServerVersion() {
        try {
            final String v = Bukkit.getServer().getClass().getPackage().getName()
                    .substring(Bukkit.getServer().getClass().getPackage().getName().lastIndexOf(".") + 1); //$NON-NLS-1$
            if (v.startsWith("v1_7_R1")) //$NON-NLS-1$
            {
                return MinecraftVersionsType.V1_7_R1;
            }
            if (v.startsWith("v1_7_R2")) //$NON-NLS-1$
            {
                return MinecraftVersionsType.V1_7_R2;
            }
            if (v.startsWith("v1_7_R3")) //$NON-NLS-1$
            {
                return MinecraftVersionsType.V1_7_R3;
            }
            if (v.startsWith("v1_7_R4")) //$NON-NLS-1$
            {
                return MinecraftVersionsType.V1_7_R4;
            }
            if (v.startsWith("v1_8_R1")) //$NON-NLS-1$
            {
                return MinecraftVersionsType.V1_8_R1;
            }
            if (v.startsWith("v1_8_R2")) //$NON-NLS-1$
            {
                return MinecraftVersionsType.V1_8_R2;
            }
            if (v.startsWith("v1_8_R3")) //$NON-NLS-1$
            {
                return MinecraftVersionsType.V1_8_R3;
            }
            if (v.startsWith("v1_9_R1")) //$NON-NLS-1$
            {
                return MinecraftVersionsType.V1_9_R1;
            }
            if (v.startsWith("v1_9_R2")) //$NON-NLS-1$
            {
                return MinecraftVersionsType.V1_9_R2;
            }
            if (v.startsWith("v1_10_R1")) //$NON-NLS-1$
            {
                return MinecraftVersionsType.V1_10_R1;
            }
            if (v.startsWith("v1_11_R1")) //$NON-NLS-1$
            {
                return MinecraftVersionsType.V1_11_R1;
            }
            if (v.startsWith("v1_12_R1")) //$NON-NLS-1$
            {
                return MinecraftVersionsType.V1_12_R1;
            }
        } catch (@SuppressWarnings("unused") Exception ex) {
            // silently ignore
        }
        return MinecraftVersionsType.Unknown;
    }

    // context

    @Override
    public <T> T getContext(Class<T> clazz) {
        return this.context.getContext(clazz);
    }

    @Override
    public <T> void setContext(Class<T> clazz, T value) {
        this.context.setContext(clazz, value);
    }

    @Override
    public <T> Function<Supplier<T>, T> createContextSupplier() {
        return this.context.createContextSupplier();
    }

    @Override
    public <T> void registerContextHandler(Plugin plugin, Class<T> clazz, ContextHandlerInterface<T> handler)
            throws McException {
        this.context.registerContextHandler(plugin, clazz, handler);
    }

    @Override
    public void runInNewContext(McRunnable runnable) throws McException {
        this.context.runInNewContext(runnable);
    }

    @Override
    public void runInNewContext(Event event, CommandInterface command, McPlayerInterface player, ZoneInterface zone,
            ComponentInterface component, McRunnable runnable) throws McException {
        this.context.runInNewContext(event, command, player, zone, component, runnable);
    }

    @Override
    public void runInCopiedContext(McRunnable runnable) throws McException {
        this.context.runInCopiedContext(runnable);
    }

    @Override
    public <T> T calculateInNewContext(McSupplier<T> runnable) throws McException {
        return this.context.calculateInNewContext(runnable);
    }

    @Override
    public <T> T calculateInNewContext(Event event, CommandInterface command, McPlayerInterface player,
            ZoneInterface zone, ComponentInterface component, McSupplier<T> runnable) throws McException {
        return this.context.calculateInNewContext(event, command, player, zone, component, runnable);
    }

    @Override
    public <T> T calculateInCopiedContext(McSupplier<T> runnable) throws McException {
        return this.context.calculateInCopiedContext(runnable);
    }

    @Override
    public void runInNewContextUnchecked(Runnable runnable) {
        this.context.runInNewContextUnchecked(runnable);
    }

    @Override
    public void runInNewContextUnchecked(Event event, CommandInterface command, McPlayerInterface player,
            ZoneInterface zone, ComponentInterface component, Runnable runnable) {
        this.context.runInNewContextUnchecked(event, command, player, zone, component, runnable);
    }

    @Override
    public void runInCopiedContextUnchecked(Runnable runnable) {
        this.context.runInCopiedContextUnchecked(runnable);
    }

    @Override
    public <T> T calculateInNewContextUnchecked(Supplier<T> runnable) {
        return this.context.calculateInNewContextUnchecked(runnable);
    }

    @Override
    public <T> T calculateInNewContextUnchecked(Event event, CommandInterface command, McPlayerInterface player,
            ZoneInterface zone, ComponentInterface component, Supplier<T> runnable) {
        return this.context.calculateInNewContextUnchecked(event, command, player, zone, component, runnable);
    }

    @Override
    public <T> T calculateInCopiedContextUnchecked(Supplier<T> runnable) {
        return this.context.calculateInCopiedContextUnchecked(runnable);
    }

    @Override
    public BukkitTask runTask(Plugin plugin, ContextRunnable task) throws IllegalArgumentException {
        return this.context.runTask(plugin, task);
    }

    @Override
    public BukkitTask runTaskAsynchronously(Plugin plugin, ContextRunnable task) throws IllegalArgumentException {
        return this.context.runTaskAsynchronously(plugin, task);
    }

    @Override
    public BukkitTask runTaskLater(Plugin plugin, long delay, ContextRunnable task)
            throws IllegalArgumentException {
        return this.context.runTaskLater(plugin, delay, task);
    }

    @Override
    public BukkitTask runTaskLaterAsynchronously(Plugin plugin, long delay, ContextRunnable task)
            throws IllegalArgumentException {
        return this.context.runTaskLaterAsynchronously(plugin, delay, task);
    }

    @Override
    public BukkitTask runTaskTimer(Plugin plugin, long delay, long period, ContextRunnable task)
            throws IllegalArgumentException {
        return this.context.runTaskTimer(plugin, delay, period, task);
    }

    @Override
    public BukkitTask runTaskTimerAsynchronously(Plugin plugin, long delay, long period, ContextRunnable task)
            throws IllegalArgumentException {
        return this.context.runTaskLaterAsynchronously(plugin, delay, task);
    }

    // messages

    @Override
    public MessagesConfigInterface getMessagesFromMsg(LocalizedMessageInterface item) {
        return this.msgService.getMessagesFromMsg(item);
    }

    /**
     * Hook into message service.
     * 
     * @param func
     *            handler for custom messaging service.
     */
    public void hookMessageService(Function<MessageServiceInterface, MessageServiceInterface> func) {
        this.msgService = func.apply(this.msgService);
    }

    @Override
    public ConfigInterface getConfigFromCfg(ConfigurationValueInterface item) {
        final Plugin plugin = this.enumService.getPlugin(item);
        if (plugin == null) {
            return null;
        }
        final Map<Class<?>, McSupplier<File>> map = this.configProviders.get(plugin);
        if (map != null) {
            final McSupplier<File> supplier = map.get(item.getClass());
            if (supplier != null) {
                try {
                    final File dataFolder = supplier.get();
                    return this.configurationsPerDataFolder.computeIfAbsent(plugin, key -> new HashMap<>())
                            .computeIfAbsent(dataFolder, key -> new ConfigImpl(dataFolder, this.enumService));
                } catch (McException e) {
                    this.getLogger().log(Level.WARNING, "Exception while fetching custom config", e); //$NON-NLS-1$
                    throw new IllegalStateException("Exception while fetching custom config", e); //$NON-NLS-1$
                }
            }
        }
        return this.configurations.computeIfAbsent(plugin, (key) -> new ConfigImpl(plugin, this.enumService));
    }

    @Override
    public void registerFileProvider(Plugin plugin, Class<? extends ConfigurationValueInterface> clazz,
            McSupplier<File> provider) {
        this.configProviders.computeIfAbsent(plugin, (key) -> new HashMap<>()).put(clazz, provider);
    }

    // events

    /**
     * Player join event.
     * 
     * @param evt
     *            event
     */
    @EventHandler(priority = EventPriority.HIGHEST)
    public void onPlayerJoin(PlayerJoinEvent evt) {
        final Player player = evt.getPlayer();

        if (ObjectServiceInterface.instance().isHuman(player)) {
            return;
        }

        while (!this.bungeeQueue.isEmpty()) {
            this.bungeeQueue.poll().accept(player);
        }

        if (this.getMinecraftVersion().isBelow(MinecraftVersionsType.V1_8_R3)) {
            NetworkManager1_8.hookResourcePackStatus(player,
                    new ResourcePackHandler(this.players, this.itemService));
        }

        // TODO Why do we need to register bungeecord here???
        // TODO Disable Server-To-Server and BungeeCord in Non-BungeeCord environments
        final PlayerManagerInterface playerManager = Bukkit.getServicesManager().load(NmsFactory.class)
                .create(PlayerManagerInterface.class);
        playerManager.registerChannelEx(player, MCLIB_SERVER_TO_CLIENT_CHANNEL);
        playerManager.registerChannelEx(player, MCLIB_SERVER_TO_SERVER_CHANNEL);
        playerManager.registerChannelEx(player, BUNGEECORD_CHANNEL);
        this.players.onPlayerJoin(evt);

        // try to ping the client mod.
        final PingData ping = new PingData();
        ping.setLogin(true);
        ping.setApi(this.getApiVersion());
        this.itemService.populate(ping);
        final DataSection section = new MemoryDataSection();
        section.set("KEY", CoreMessages.Ping.name()); //$NON-NLS-1$
        ping.write(section.createSection("data")); //$NON-NLS-1$
        this.players.getPlayer(player).sendToClient(MclibCommunication.ClientServerCore, section);

        // resources
        if (ResourceServiceInterface.instance().isAutoResourceDownload()) {
            new BukkitRunnable() {

                @Override
                public void run() {
                    if (player.isOnline()) {
                        player.setResourcePack(ResourceServiceInterface.instance().getDownloadUrl());
                    }
                }
            }.runTaskLater(this, ResourceServiceInterface.instance().getAutoResourceTicks());
        }

        // clear inventory
        new BukkitRunnable() {

            @Override
            public void run() {
                MclibPlugin.this.itemService.clearTools(player.getInventory());
            }
        }.runTaskLater(this, 2);

        // hide dummy humans
        new BukkitRunnable() {

            @Override
            public void run() {
                if (player.isOnline()) {
                    final NmsFactory factory = Bukkit.getServicesManager().load(NmsFactory.class);
                    factory.create(EntityHelperInterface.class).playerOnline(player);
                    factory.create(SignHelperInterface.class).playerOnline(player);
                    factory.create(HologramHelperInterface.class).playerOnline(evt.getPlayer());
                }
            }
        }.runTaskLater(this, 10);
    }

    /**
     * Player quit event.
     * 
     * @param evt
     *            event
     */
    @EventHandler(priority = EventPriority.HIGHEST)
    public void onPlayerQuit(PlayerQuitEvent evt) {
        if (!ObjectServiceInterface.instance().isHuman(evt.getPlayer())) {
            final NmsFactory factory = Bukkit.getServicesManager().load(NmsFactory.class);
            factory.create(EntityHelperInterface.class).playerOffline(evt.getPlayer());
            factory.create(SignHelperInterface.class).playerOffline(evt.getPlayer());
            factory.create(HologramHelperInterface.class).playerOffline(evt.getPlayer());
            this.players.onPlayerQuit(evt);
        }
    }

    // library

    @Override
    public MinecraftVersionsType getMinecraftVersion() {
        return SERVER_VERSION;
    }

    // gui

    @Override
    public Locale getDefaultLocale() {
        return new Locale(McCoreConfig.DefaultLocale.getString());
    }

    @Override
    public Collection<Locale> getMainLocales() {
        return Arrays.stream(McCoreConfig.MainLocales.getStringList()).map(Locale::new)
                .collect(Collectors.toList());
    }

    @Override
    public void removeMainLocale(Locale locale) throws McException {
        final String[] main = McCoreConfig.MainLocales.getStringList();
        if (main.length == 1 && main[0].equals(locale.toString())) {
            // TODO throw exception
        }
        McCoreConfig.MainLocales
                .setStringList(Arrays.stream(main).filter(p -> p.equals(locale.toString())).toArray(String[]::new));
        McCoreConfig.MainLocales.saveConfig();
    }

    @Override
    public void addMainLocale(Locale locale) throws McException {
        final String[] main = McCoreConfig.MainLocales.getStringList();
        for (final String l : main) {
            if (l.equals(locale.toString())) {
                return;
            }
        }
        final String[] result = Arrays.copyOf(main, main.length + 1);
        result[main.length] = locale.toString();
        McCoreConfig.MainLocales.setStringList(result);
        McCoreConfig.MainLocales.saveConfig();
    }

    @Override
    public void setDefaultLocale(Locale locale) throws McException {
        McCoreConfig.DefaultLocale.setString(locale.toString());
        McCoreConfig.DefaultLocale.saveConfig();
    }

    @Override
    public boolean debug() {
        return McCoreConfig.Debug.getBoolean();
    }

    @Override
    public void setDebug(boolean enabled) {
        McCoreConfig.Debug.setBoolean(enabled);
        McCoreConfig.Debug.saveConfig();
    }

    /**
     * event handler for plugin disable events.
     * 
     * @param evt
     *            event
     */
    @EventHandler
    public void onPluginDisable(PluginDisableEvent evt) {
        if (!evt.getPlugin().isEnabled()) {
            this.enumService.unregisterAllEnumerations(evt.getPlugin());
            this.objectsManager.onDisable(evt.getPlugin());
            this.players.onDisable(evt.getPlugin());
            this.eventBus.onDisable(evt.getPlugin());
            this.configurations.remove(evt.getPlugin());
            this.configProviders.remove(evt.getPlugin());
            this.msgService.unregisterPlaceholders(evt.getPlugin());
            this.msgService.unregisterPlaceholderListener(evt.getPlugin());
        }
    }

    /**
     * event handler for plugin enable events.
     * 
     * @param evt
     *            event
     */
    @EventHandler
    public void onPluginEnable(PluginEnableEvent evt) {
        if (evt.getPlugin().isEnabled()) {
            this.objectsManager.onEnable(evt.getPlugin());
            this.itemService.onEnable(evt.getPlugin());
        }
    }

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        if (command.getName().equals("mclib")) //$NON-NLS-1$
        {
            if (args != null && args.length == 2 && args[0].equals("rawcommand") && sender instanceof Player) //$NON-NLS-1$
            {
                final McPlayerImpl player = this.players.getPlayer((Player) sender);
                try {
                    final UUID uuid = UUID.fromString(args[1]);
                    player.onRawCommand(uuid);
                } catch (IllegalArgumentException ex) {
                    this.getLogger().log(Level.FINE, "Problems parsing rawmessage. no uuid: " + args[1], ex); //$NON-NLS-1$
                }
            } else {
                CommandImpl.onCommand(sender, command, label, args, this.mclibCommand);
            }
        }
        return false;
    }

    @Override
    public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
        if (command.getName().equals("mclib")) //$NON-NLS-1$
        {
            return CommandImpl.onTabComplete(sender, command, alias, args, this.mclibCommand);
        }
        return null;
    }

    @Override
    public void registerPeerHandler(Plugin plugin, CommunicationEndpointId id, CommunicationPeerHandler handler) {
        synchronized (this.peerEndpoints) {
            Map<String, CommunicationEndpointId> map = this.peerEndpoints.get(id.getClass().getName());
            if (map == null) {
                map = new HashMap<>();
                this.peerEndpoints.put(id.getClass().getName(), map);
            }
            if (map.containsKey(id.name())) {
                throw new IllegalStateException("Duplicate registration of communication endpoint."); //$NON-NLS-1$
            }
            map.put(id.name(), id);
            this.peerHandlers.put(id, handler);
            this.peerEndpointPlugins.computeIfAbsent(plugin.getName(), k -> new ArrayList<>()).add(id);
            this.endpointTypes.put(id, Boolean.TRUE);
        }
    }

    @Override
    public void registerBungeeHandler(Plugin plugin, CommunicationEndpointId id,
            CommunicationBungeeHandler handler) {
        synchronized (this.bungeeEndpoints) {
            Map<String, CommunicationEndpointId> map = this.bungeeEndpoints.get(id.getClass().getName());
            if (map == null) {
                map = new HashMap<>();
                this.bungeeEndpoints.put(id.getClass().getName(), map);
            }
            if (map.containsKey(id.name())) {
                throw new IllegalStateException("Duplicate registration of communication endpoint."); //$NON-NLS-1$
            }
            map.put(id.name(), id);
            this.bungeeHandlers.put(id, handler);
            this.bungeeEndpointPlugins.computeIfAbsent(plugin.getName(), k -> new ArrayList<>()).add(id);
            this.endpointTypes.put(id, Boolean.FALSE);
        }
    }

    /**
     * Returns the endpoint for given class and element.
     * 
     * @param clazz
     *            enpoint class
     * @param name
     *            enumeration name
     * @return endpoint or {@code null} if it does not exist.
     */
    private CommunicationEndpointId getPeerEndpoint(String clazz, String name) {
        synchronized (this.peerEndpoints) {
            final Map<String, CommunicationEndpointId> map = this.peerEndpoints.get(clazz);
            if (map != null) {
                return map.get(name);
            }
        }
        return null;
    }

    /**
     * Returns the endpoint for given class and element.
     * 
     * @param clazz
     *            entpoint class
     * @param name
     *            enumeration name
     * @return endpoint or {@code null} if it does not exist.
     */
    private CommunicationEndpointId getServerEndpoint(String clazz, String name) {
        synchronized (this.bungeeEndpoints) {
            final Map<String, CommunicationEndpointId> map = this.bungeeEndpoints.get(clazz);
            if (map != null) {
                return map.get(name);
            }
        }
        return null;
    }

    @Override
    public void broadcastClients(CommunicationEndpointId id, DataSection... data) {
        if (data != null) {
            for (final DataSection section : data) {
                final ByteArrayDataOutput out = ByteStreams.newDataOutput();
                out.writeByte(0);
                new NetMessage(id, section).toBytes(out);
                byte[] bytes = out.toByteArray();
                if (this.getLogger().isLoggable(Level.FINEST)) {
                    this.getLogger().finest("Sending NetMessage to all players\n" + Arrays.toString(bytes)); //$NON-NLS-1$
                }
                // TODO find a way to broadcast to all players connected to any server in bungee cord network
                Bukkit.getServer().sendPluginMessage(this, MCLIB_SERVER_TO_CLIENT_CHANNEL, bytes);
            }
        }
    }

    @Override
    public void broadcastServers(CommunicationEndpointId id, DataSection... data) {
        if (data != null) {
            for (final DataSection section : data) {
                // TODO only works if both servers have online players
                // TODO if target servers do not have online players the messages should be queued by bungeecord.
                // TODO ensure that we are really within bungeecord environments, else this will be sent to clients (not good)
                final ByteArrayDataOutput out2 = ByteStreams.newDataOutput();
                final ByteArrayDataOutput out = ByteStreams.newDataOutput();
                out2.writeUTF("Forward"); //$NON-NLS-1$
                out2.writeUTF("ALL"); //$NON-NLS-1$
                out2.writeUTF(MCLIB_SERVER_TO_SERVER_CHANNEL);
                out.writeByte(0);
                new NetMessage(id, section).toBytes(out);
                byte[] bytes = out.toByteArray();
                if (this.getLogger().isLoggable(Level.FINEST)) {
                    this.getLogger().finest("Sending NetMessage to all servers\n" + Arrays.toString(bytes)); //$NON-NLS-1$
                }
                out2.writeShort(bytes.length);
                out2.write(bytes);
                final ObjectServiceInterface osi = ObjectServiceInterface.instance();
                final Optional<? extends Player> player = Bukkit.getOnlinePlayers().stream()
                        .filter(p -> !osi.isHuman(p)).findFirst();
                if (player.isPresent()) {
                    // bungee cord ensures this is not send to the client.
                    player.get().sendPluginMessage(this, BUNGEECORD_CHANNEL, bytes);
                } else {
                    this.bungeeQueue.add(p -> p.sendPluginMessage(this, BUNGEECORD_CHANNEL, bytes));
                }
            }
        }
    }

    @Override
    public void send(CommunicationEndpointId id, DataSection... data) {
        // check if we have a server-to-server endpoint or server-to-client endpoint
        if (this.endpointTypes.get(id)) {
            // server to player
            final McPlayerInterface player = this.getCurrentPlayer();
            if (player == null) {
                this.getLogger().fine("Trying to send data to unknown player (invalid context)"); //$NON-NLS-1$
            } else if (data != null) {
                for (final DataSection section : data) {
                    final ByteArrayDataOutput out = ByteStreams.newDataOutput();
                    out.writeByte(0);
                    new NetMessage(id, section).toBytes(out);
                    byte[] bytes = out.toByteArray();
                    if (this.getLogger().isLoggable(Level.FINEST)) {
                        this.getLogger().finest("Sending NetMessage to player " + player.getPlayerUUID() + "\n" //$NON-NLS-1$//$NON-NLS-2$
                                + Arrays.toString(bytes));
                    }
                    player.getBukkitPlayer().sendPluginMessage(this, MCLIB_SERVER_TO_CLIENT_CHANNEL, bytes);
                }
            }
        } else {
            // server to server means: broadcast to everyone
            this.broadcastServers(id, data);
        }
    }

    /**
     * Sign delete event.
     * 
     * @param evt
     *            event
     */
    @EventHandler(priority = EventPriority.LOWEST)
    public void onSignDelete(BlockBreakEvent evt) {
        this.objectsManager.onSignDelete(evt);
    }

    @Override
    public void onPluginMessageReceived(String channel, Player player, byte[] buf) {
        if (channel.equals(BUNGEECORD_CHANNEL)) {
            // TODO ensure this comes from bungee
            final ByteArrayDataInput input = ByteStreams.newDataInput(buf);
            final String sub = input.readUTF();
            if (sub.equals("GetServer")) //$NON-NLS-1$
            {
                final String serverName = input.readUTF();
                McCoreConfig.BungeeServerName.setString(serverName);
                McCoreConfig.BungeeServerName.saveConfig();
            } else if (sub.equals("GetServers")) //$NON-NLS-1$
            {
                String[] serverList = input.readUTF().split(", "); //$NON-NLS-1$
                final String myself = this.currentBungee.getName();
                synchronized (this.bungeeServers) {
                    final Set<String> failed = new HashSet<>(this.bungeeServers.keySet());
                    for (final String serverName : serverList) {
                        if (!serverName.equals(myself)) {
                            if (!failed.remove(serverName)) {
                                this.bungeeServers.put(serverName, new RemoteBungeeServer(serverName));
                            }
                        }
                    }
                    failed.forEach(this.bungeeServers::remove);
                }
            }
        } else if (channel.equals(MCLIB_SERVER_TO_CLIENT_CHANNEL)) {
            // TODO ensure this comes from client
            // message from client
            final McPlayerInterface mcp = this.players.getPlayer(player);
            try {
                this.runInNewContext(() -> {
                    this.setContext(McPlayerInterface.class, mcp);
                    final NetMessage msg = new NetMessage();
                    final ByteArrayDataInput input = ByteStreams.newDataInput(buf);
                    if (input.readByte() == 0) // forge NetMessage byte code.
                    {
                        msg.fromBytes(input, this::getPeerEndpoint);

                        if (msg.getEndpoint() != null) {
                            final CommunicationPeerHandler handler = this.peerHandlers.get(msg.getEndpoint());
                            if (handler != null) {
                                handler.handleIncomming(mcp, msg.getEndpoint(), msg.getData());
                            }
                        }
                    }
                });
            } catch (McException ex) {
                this.getLogger().log(Level.WARNING, "Problems handling client message", ex); //$NON-NLS-1$
            }
        } else if (channel.equals(MCLIB_SERVER_TO_SERVER_CHANNEL)) {
            // TODO ensure this comes from bungee
            // message from bungee network
            final NetMessage msg = new NetMessage();
            final ByteArrayDataInput input = ByteStreams.newDataInput(buf);
            if (input.readByte() == 1) // forge NetMessage byte code.
            {
                final String sendingServer = input.readUTF();
                final String receivingServer = input.readUTF();
                final String myName = BungeeServiceInterface.instance().getCurrent().getName();
                if (myName.equals(receivingServer) || "*".equals(receivingServer)) //$NON-NLS-1$
                {
                    msg.fromBytes(input, this::getServerEndpoint);

                    if (msg.getEndpoint() != null) {
                        final CommunicationBungeeHandler handler = this.bungeeHandlers.get(msg.getEndpoint());
                        if (handler != null) {
                            final BungeeServerInterface sender = BungeeServiceInterface.instance()
                                    .getServer(sendingServer);
                            handler.handleIncomming(sender, msg.getEndpoint(), msg.getData());
                        }
                    }
                }
            }
        }
    }

    @Override
    public void removeAllCommunicationEndpoints(Plugin plugin) {
        synchronized (this.peerEndpoints) {
            final List<CommunicationEndpointId> ids = this.peerEndpointPlugins.get(plugin.getName());
            if (ids != null) {
                for (final CommunicationEndpointId id : ids) {
                    this.peerHandlers.remove(id);
                    this.peerEndpoints.get(id.getClass().getName()).remove(id.name());
                    this.endpointTypes.remove(id);
                }
                this.peerEndpointPlugins.remove(plugin.getName());
            }
        }
        synchronized (this.bungeeEndpoints) {
            final List<CommunicationEndpointId> ids = this.bungeeEndpointPlugins.get(plugin.getName());
            if (ids != null) {
                for (final CommunicationEndpointId id : ids) {
                    this.bungeeHandlers.remove(id);
                    this.bungeeEndpoints.get(id.getClass().getName()).remove(id.name());
                    this.endpointTypes.remove(id);
                }
                this.bungeeEndpointPlugins.remove(plugin.getName());
            }
        }
    }

    @Override
    public <T extends ExtensionInterface> void register(Plugin plugin, ExtensionPointInterface<T> extPoint,
            T extension) {
        this.extensions.register(plugin, extPoint, extension);
    }

    @Override
    public <T extends ExtensionInterface> void remove(Plugin plugin, ExtensionPointInterface<T> extPoint,
            T extension) {
        this.extensions.unregister(plugin, extPoint, extension);
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T extends ExtensionInterface> Iterable<T> getExtensions(ExtensionPointInterface<? extends T> extPoint) {
        return (Iterable<T>) this.extensions.get(extPoint);
    }

    @Override
    public void removeAllExtensions(Plugin plugin) {
        this.extensions.unregisterAll(plugin);
    }

    /**
     * Communication handler for incoming network traffic.
     */
    private class MclibCoreHandler implements CommunicationPeerHandler {

        /**
         * Constructor.
         */
        public MclibCoreHandler() {
            // empty
        }

        @Override
        public void handleIncomming(McPlayerInterface player, CommunicationEndpointId id, DataSection section) {
            // silently drop invalid messages.
            if (!section.contains("KEY")) //$NON-NLS-1$
            {
                return;
            }

            try {
                final String key = section.getString("KEY"); //$NON-NLS-1$
                if (key.equals(CoreMessages.ActionPerformed.name())) {
                    // forward to smart gui
                    ((McPlayerImpl) player)
                            .parseActionPerformed(section.getFragment(ActionPerformedData.class, "data")); //$NON-NLS-1$
                } else if (key.equals(CoreMessages.WinClosed.name())) {
                    // forward to smart gui
                    ((McPlayerImpl) player).parseWinClosed(section.getFragment(WinClosedData.class, "data")); //$NON-NLS-1$
                } else if (key.equals(CoreMessages.QueryFormRequest.name())) {
                    // client has the mod installed.
                    ((McPlayerImpl) player)
                            .parseFormRequest(section.getFragment(QueryFormRequestData.class, "data")); //$NON-NLS-1$
                } else if (key.equals(CoreMessages.Pong.name())) {
                    // client has the mod installed.
                    MclibPlugin.this.players.parsePong(player, section.getFragment(PongData.class, "data")); //$NON-NLS-1$
                }
                // otherwise silently ignore the invalid message.
            } catch (McException e) {
                MclibPlugin.this.getLogger().log(Level.WARNING, "Problems handling client core message", e); //$NON-NLS-1$
            }
        }

    }

    @Override
    public BungeeServerInterface getCurrent() {
        return this.currentBungee;
    }

    @Override
    public BungeeServerInterface getServer(String name) {
        synchronized (this.bungeeServers) {
            return this.bungeeServers.get(name);
        }
    }

    @Override
    public Collection<BungeeServerInterface> getServers() {
        synchronized (this.bungeeServers) {
            return new ArrayList<>(this.bungeeServers.values());
        }
    }

    /**
     * The local bungee server (myself).
     */
    private final class LocalBungeeServer implements BungeeServerInterface {

        /**
         * Constructor.
         */
        public LocalBungeeServer() {
            // empty
        }

        @Override
        public String getName() {
            return McCoreConfig.BungeeServerName.getString();
        }

        @Override
        public void send(CommunicationEndpointId id, DataSection... data) {
            // loop back
            if (data != null) {
                final CommunicationBungeeHandler handler = MclibPlugin.this.bungeeHandlers.get(id);
                if (handler != null) {
                    for (final DataSection section : data) {
                        handler.handleIncomming(this, id, section);
                    }
                }
            }
        }

        @Override
        public void transferPlayer(McPlayerInterface player) {
            // TODO for remote players support teleport to myself
        }

    }

    /**
     * The remote bungee server (available through bungeecord).
     */
    private final class RemoteBungeeServer implements BungeeServerInterface {

        /** bungee coord name. */
        private String name;

        /**
         * Constructor.
         * 
         * @param name
         *            server name
         */
        public RemoteBungeeServer(String name) {
            this.name = name;
        }

        @Override
        public String getName() {
            return this.name;
        }

        @Override
        public void send(CommunicationEndpointId id, DataSection... data) {
            // loop back
            if (data != null) {
                for (final DataSection section : data) {
                    // TODO only works if both servers have online players
                    // TODO if target servers do not have online players the messages should be queued by bungeecord.
                    // TODO ensure that we are really within bungeecord environments, else this will be sent to clients (not good)
                    final ByteArrayDataOutput out2 = ByteStreams.newDataOutput();
                    final ByteArrayDataOutput out = ByteStreams.newDataOutput();
                    out2.writeUTF("Forward"); //$NON-NLS-1$
                    out2.writeUTF(this.name);
                    out2.writeUTF(MCLIB_SERVER_TO_SERVER_CHANNEL);
                    out.writeByte(0);
                    new NetMessage(id, section).toBytes(out);
                    byte[] bytes = out.toByteArray();
                    if (MclibPlugin.this.getLogger().isLoggable(Level.FINEST)) {
                        MclibPlugin.this.getLogger().finest(
                                "Sending NetMessage to server " + this.name + "\n" + Arrays.toString(bytes)); //$NON-NLS-1$ //$NON-NLS-2$
                    }
                    out2.writeShort(bytes.length);
                    out2.write(bytes);
                    final ObjectServiceInterface osi = ObjectServiceInterface.instance();
                    final Optional<? extends Player> player = Bukkit.getOnlinePlayers().stream()
                            .filter(p -> !osi.isHuman(p)).findFirst();
                    if (player.isPresent()) {
                        // bungee cord ensures this is not send to the client.
                        player.get().sendPluginMessage(MclibPlugin.this, BUNGEECORD_CHANNEL, bytes);
                    } else {
                        MclibPlugin.this.bungeeQueue
                                .add(p -> p.sendPluginMessage(MclibPlugin.this, BUNGEECORD_CHANNEL, bytes));
                    }
                }
            }
        }

        @Override
        public void transferPlayer(McPlayerInterface player) {
            final ByteArrayDataOutput out = ByteStreams.newDataOutput();
            out.writeUTF("Connect"); //$NON-NLS-1$
            out.writeUTF(this.getName());
            player.getBukkitPlayer().sendPluginMessage(MclibPlugin.this, BUNGEECORD_CHANNEL, out.toByteArray());
        }

    }

    @Override
    public RawMessageInterface createRaw() {
        return new RawMessage();
    }

    @Override
    public <EVT extends MinecraftEvent<?, EVT>> void registerHandler(Plugin plugin, Class<EVT> clazz,
            McConsumer<EVT> handler) {
        this.eventBus.registerHandler(plugin, clazz, handler);
    }

    @Override
    public void registerHandlers(Plugin plugin, McListener listener) {
        this.eventBus.registerHandlers(plugin, listener);
    }

    @Override
    public <EVT extends MinecraftEvent<?, EVT>> void unregisterHandler(Plugin plugin, Class<EVT> clazz,
            McConsumer<EVT> handler) {
        this.eventBus.unregisterHandler(plugin, clazz, handler);
    }

    @Override
    public void unregisterHandlers(Plugin plugin, McListener listener) {
        this.eventBus.unregisterHandlers(plugin, listener);
    }

    @Override
    public <T extends Event, EVT extends MinecraftEvent<T, EVT>> void handle(Class<EVT> eventClass, EVT event) {
        this.eventBus.handle(eventClass, event);
    }

    @Override
    public DataSection readYmlFile(File file) throws IOException {
        return new YmlFile(file);
    }

    @Override
    public DataSection readYmlFile(InputStream file) throws IOException {
        return new YmlFile(file);
    }

    @Override
    public void saveYmlFile(DataSection section, File file) throws IOException {
        if (section instanceof YmlFile) {
            ((YmlFile) section).saveFile(file);
        } else {
            final YmlFile yml = new YmlFile();
            section.getValues(true).forEach(yml::set);
            yml.saveFile(file);
        }
    }

    /**
     * enum value factory method.
     * 
     * @param <T>
     *            target class (interface)
     * @param plugin
     *            plugin name
     * @param name
     *            enumeration name
     * @param clazz
     *            target class (interface)
     * @return enumeration value or {@code null} if it does not exist
     */
    private static <T extends UniqueEnumerationValue> T create(String plugin, String name, Class<T> clazz) {
        return instance.enumService.create(plugin, name, clazz);
    }

    @Override
    public <EVT extends Event & MinecraftEvent<EVT, EVT>> void registerEvent(Plugin plugin, Class<EVT> clazz) {
        Bukkit.getServicesManager().load(EventSystemInterface.class).registerEvent(plugin, clazz);
    }

    @Override
    public ClickGuiItem createGuiEditorItem(EditableValue config, Runnable onChange, GuiItemHandler back,
            GuiItemHandler home, McRunnable contextProvider) throws McException {
        return AbstractConfigOption.create(config).getItem(onChange, back, home, contextProvider);
    }

    @Override
    public ClickGuiItem createGuiEditorItem(EditableValue config, Runnable onChange, GuiItemHandler back,
            GuiItemHandler home) throws McException {
        return AbstractConfigOption.create(config).getItem(onChange, back, home, null);
    }

    @Override
    public ClickGuiItem createGuiEditorItem(LocalizedMessageInterface title, LocalizedConfigLine msg,
            Runnable onChange, GuiItemHandler back, GuiItemHandler home) throws McException {
        return new ClickGuiItem(ItemServiceInterface.instance().createItem(CommonItems.App_Text), title,
                (p, s, g) -> {
                    p.openClickGui(new SimpleClickGui(ClickGuis.List, new LocalizedLinesList(
                            LocalizedLinesList.Messages.Title, msg, m -> onChange.run(), back), 6));
                });
    }

    @Override
    public ClickGuiItem createGuiEditorItem(LocalizedMessageInterface title, LocalizedConfigLine msg,
            Runnable onChange, GuiItemHandler back, GuiItemHandler home, McRunnable contextProvider)
            throws McException {
        return new ClickGuiItem(ItemServiceInterface.instance().createItem(CommonItems.App_Text), title,
                (p, s, g) -> {
                    p.openClickGui(new SimpleClickGui(ClickGuis.List,
                            new LocalizedLinesList(LocalizedLinesList.Messages.Title, msg,
                                    m -> McLibInterface.instance().runInNewContext(() -> {
                                        contextProvider.run();
                                        onChange.run();
                                    }), back),
                            6));
                });
    }

    @Override
    public ClickGuiItem createGuiEditorItem(LocalizedMessageInterface title, LocalizedConfigString msg,
            Runnable onChange, GuiItemHandler back, GuiItemHandler home) throws McException {
        return new ClickGuiItem(ItemServiceInterface.instance().createItem(CommonItems.App_Text), title,
                (p, s, g) -> {
                    p.openClickGui(new SimpleClickGui(ClickGuis.List, new LocalizedStringList(
                            LocalizedStringList.Messages.Title, msg, m -> onChange.run(), back), 6));
                });
    }

    @Override
    public ClickGuiItem createGuiEditorItem(LocalizedMessageInterface title, LocalizedConfigString msg,
            Runnable onChange, GuiItemHandler back, GuiItemHandler home, McRunnable contextProvider)
            throws McException {
        return new ClickGuiItem(ItemServiceInterface.instance().createItem(CommonItems.App_Text), title,
                (p, s, g) -> {
                    p.openClickGui(new SimpleClickGui(ClickGuis.List,
                            new LocalizedStringList(LocalizedStringList.Messages.Title, msg,
                                    m -> McLibInterface.instance().runInNewContext(() -> {
                                        contextProvider.run();
                                        onChange.run();
                                    }), back),
                            6));
                });
    }

    /**
     * handle inventory close event.
     * 
     * @param evt
     *            event
     */
    @EventHandler
    public void onClose(InventoryCloseEvent evt) {
        if (evt.getInventory() instanceof InventoryListener) {
            ((InventoryListener) evt.getInventory()).handle(evt);
        }
    }

    @Override
    public String replacePlaceholders(Locale locale, String msg) {
        return this.msgService.replacePlaceholders(locale, msg);
    }

    @Override
    public void registerPlaceholders(Plugin plugin, String prefix, Placeholder placeholder) {
        this.msgService.registerPlaceholders(plugin, prefix, placeholder);
    }

    @Override
    public void unregisterPlaceholders(Plugin plugin, String prefix, Placeholder placeholder) {
        this.msgService.unregisterPlaceholders(plugin, prefix, placeholder);
    }

    @Override
    public void unregisterPlaceholders(Plugin plugin) {
        this.msgService.unregisterPlaceholders(plugin);
    }

    @Override
    public void registerPlaceholderListener(Plugin plugin, String[][] placeholder, PlaceholderListener listener) {
        this.msgService.registerPlaceholderListener(plugin, placeholder, listener);
    }

    @Override
    public void unregisterPlaceholderListener(Plugin plugin, String[][] placeholder, PlaceholderListener listener) {
        this.msgService.unregisterPlaceholderListener(plugin, placeholder, listener);
    }

    @Override
    public void unregisterPlaceholderListener(Plugin plugin) {
        this.msgService.unregisterPlaceholderListener(plugin);
    }

    @Override
    public String[][] getPlaceholders(String msg) {
        return this.msgService.getPlaceholders(msg);
    }

    @Override
    public void notifyPlaceholderChanges(String[][] placeholders) {
        this.msgService.notifyPlaceholderChanges(placeholders);
    }

}