package me.km.snuviscript;

import com.mojang.authlib.GameProfile;
import com.mojang.brigadier.StringReader;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;
import me.hammerle.snuviscript.code.Script;
import me.hammerle.snuviscript.code.ScriptManager;
import me.hammerle.snuviscript.code.SnuviUtils;
import me.hammerle.snuviscript.inputprovider.InputProvider;
import me.km.Server;
import me.km.blockprotections.IBlockProtection;
import me.km.databank.DataBank;
import me.km.utils.Location;
import me.km.utils.Utils;
import me.km.world.WorldManager;
import me.km.entities.EntityHuman;
import me.km.entities.EntityItemProjectile;
import me.km.entities.ModEntities;
import me.km.inventory.InventoryUtils;
import me.km.utils.ItemStackUtils;
import me.km.utils.ReflectionUtils;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.Entity;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.network.ThreadQuickExitException;
import net.minecraft.potion.PotionUtils;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.DamageSource;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import me.km.events.CustomEventCaller;
import me.km.events.PlayerMoveData;
import me.km.inventory.CustomContainer;
import me.km.inventory.ModInventory;
import me.km.networking.ModPacketHandler;
import me.km.overrides.ModEntityPlayerMP;
import me.km.permissions.PermissionManager;
import me.km.playerbank.IPlayerBank;
import me.km.plots.PlotMap.Plot;
import me.km.plots.WorldPlotMap;
import me.km.scheduler.SnuviScheduler;
import me.km.utils.Mapper;
import me.km.utils.TableUtils;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.ChestBlock;
import net.minecraft.block.CropsBlock;
import net.minecraft.block.DoorBlock;
import net.minecraft.command.ICommandSource;
import net.minecraft.command.arguments.BlockStateParser;
import net.minecraft.entity.AgeableEntity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.MobEntity;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.SpawnReason;
import net.minecraft.entity.ai.attributes.AttributeModifier.Operation;
import net.minecraft.entity.effect.LightningBoltEntity;
import net.minecraft.entity.item.EnderPearlEntity;
import net.minecraft.entity.item.ExperienceBottleEntity;
import net.minecraft.entity.item.ItemEntity;
import net.minecraft.entity.item.ItemFrameEntity;
import net.minecraft.entity.monster.CreeperEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.entity.projectile.AbstractArrowEntity;
import net.minecraft.entity.projectile.ArrowEntity;
import net.minecraft.entity.projectile.DamagingProjectileEntity;
import net.minecraft.entity.projectile.DragonFireballEntity;
import net.minecraft.entity.projectile.EggEntity;
import net.minecraft.entity.projectile.FireballEntity;
import net.minecraft.entity.projectile.PotionEntity;
import net.minecraft.entity.projectile.SmallFireballEntity;
import net.minecraft.entity.projectile.SnowballEntity;
import net.minecraft.entity.projectile.SpectralArrowEntity;
import net.minecraft.entity.projectile.WitherSkullEntity;
import net.minecraft.inventory.EnderChestInventory;
import net.minecraft.inventory.EquipmentSlotType;
import net.minecraft.inventory.container.ChestContainer;
import net.minecraft.inventory.container.SimpleNamedContainerProvider;
import net.minecraft.item.ArmorItem;
import net.minecraft.item.Item;
import net.minecraft.item.Items;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.JsonToNBT;
import net.minecraft.nbt.NBTUtil;
import net.minecraft.network.play.client.CChatMessagePacket;
import net.minecraft.network.play.client.CClientStatusPacket;
import net.minecraft.network.play.server.SPlaySoundEffectPacket;
import net.minecraft.network.play.server.SSpawnPositionPacket;
import net.minecraft.network.play.server.STitlePacket;
import net.minecraft.network.play.server.SUpdateTileEntityPacket;
import net.minecraft.particles.BlockParticleData;
import net.minecraft.particles.IParticleData;
import net.minecraft.particles.ItemParticleData;
import net.minecraft.particles.ParticleType;
import net.minecraft.particles.ParticleTypes;
import net.minecraft.particles.RedstoneParticleData;
import net.minecraft.potion.Effect;
import net.minecraft.potion.EffectInstance;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.management.PlayerList;
import net.minecraft.server.management.ProfileBanEntry;
import net.minecraft.state.IProperty;
import net.minecraft.state.properties.ChestType;
import net.minecraft.stats.Stats;
import net.minecraft.tags.BlockTags;
import net.minecraft.tags.ItemTags;
import net.minecraft.tags.Tag;
import net.minecraft.tileentity.ChestTileEntity;
import net.minecraft.tileentity.MobSpawnerTileEntity;
import net.minecraft.tileentity.SignTileEntity;
import net.minecraft.tileentity.SkullTileEntity;
import net.minecraft.util.Direction;
import net.minecraft.util.NonNullList;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.RayTraceContext;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.StringTextComponent;
import net.minecraft.util.text.Style;
import net.minecraft.util.text.TextFormatting;
import net.minecraft.util.text.event.ClickEvent;
import net.minecraft.util.text.event.HoverEvent;
import net.minecraft.world.Difficulty;
import net.minecraft.world.GameRules;
import net.minecraft.world.GameType;
import net.minecraft.world.IWorld;
import net.minecraft.world.server.ServerWorld;
import net.minecraft.world.storage.WorldInfo;

public class MinecraftFunctions
{
    public static int inventoryIds = 0;

    @SuppressWarnings("")
    public static void registerFunctions(ScriptManager sm, Scripts scripts, 
            PermissionManager perms, SnuviScheduler scheduler, MinecraftServer server, 
            IPlayerBank playerBank, CustomEventCaller cec,
            IScriptBank scriptBank, DataBank dataBank, IBlockProtection blockProtection,
            WorldPlotMap plots)
    {
        // ---------------------------------------------------------------------
        // Command-library  
        // ---------------------------------------------------------------------
        sm.registerFunction("command.add", (sc, in) -> 
        {
            scripts.registerScriptCommand(in[0].getString(sc));
            return Void.TYPE;
        }); 
        sm.registerFunction("command.remove", (sc, in) -> 
        {
            scripts.unregisterScriptCommand(in[0].getString(sc));
            return Void.TYPE;
        }); 
        sm.registerFunction("command.exists", (sc, in) -> scripts.isRegisteredScriptCommand(in[0].getString(sc)));
        sm.registerFunction("command.clear", (sc, in) -> 
        {
            scripts.clearScriptCommands();
            return Void.TYPE;
        }); 
        
        // ---------------------------------------------------------------------
        // Permission-library  
        // ---------------------------------------------------------------------
        sm.registerFunction("perm.clear", (sc, in) -> 
        {
            perms.clear();
            return Void.TYPE;
        }); 
        sm.registerFunction("perm.registergroup", (sc, in) -> 
        {
            perms.registerGroupPermission(in[0].getInt(sc), in[1].getString(sc));
            return Void.TYPE;
        }); 
        sm.registerFunction("perm.registerplayer", (sc, in) -> 
        {
            perms.registerPlayerGroup(getUUID(in[0].get(sc)), in[1].getInt(sc));
            return Void.TYPE;
        }); 
        sm.registerFunction("perm.unregisterplayer", (sc, in) -> perms.unregisterPlayer(getUUID(in[0].get(sc)), in[1].getInt(sc))); 
        sm.registerFunction("perm.has", (sc, in) -> perms.hasPermission((Entity) in[0].get(sc), in[1].getString(sc))); 
        
        // ---------------------------------------------------------------------
        // Title-library  
        // ---------------------------------------------------------------------
        sm.registerFunction("table.getstart", (sc, in) -> TableUtils.getTableStart(in[0].getInt(sc), in[1].getInt(sc), in[2].getString(sc)));
        sm.registerFunction("table.getmiddle", (sc, in) -> TableUtils.getTableMiddle(in[0].getInt(sc), in[1].getInt(sc), in[2].getString(sc)));
        sm.registerFunction("table.getend", (sc, in) -> TableUtils.getTableEnd(in[0].getInt(sc), in[1].getInt(sc), in[2].getString(sc)));
        sm.registerFunction("table.get", (sc, in) -> 
        { 
            String[] columns = new String[in.length - 2];
            for(int i = 0; i < columns.length; i++)
            {
                columns[i] = in[i + 2].getString(sc);
            }
            return TableUtils.getTable(in[0].getInt(sc), in[1].getString(sc), columns);
        });
        
        // ---------------------------------------------------------------------
        // table library  
        // ---------------------------------------------------------------------
        sm.registerFunction("title.settime", (sc, in) -> 
        {
            ((ServerPlayerEntity) in[0].get(sc)).connection.sendPacket(new STitlePacket(in[1].getInt(sc), in[2].getInt(sc), in[3].getInt(sc)));
            return Void.TYPE;
        });
        sm.registerFunction("title.clear", (sc, in) -> 
        { 
            ServerPlayerEntity p = (ServerPlayerEntity) in[0].get(sc);
            p.connection.sendPacket(new STitlePacket(STitlePacket.Type.CLEAR, null));
            p.connection.sendPacket(new STitlePacket(STitlePacket.Type.RESET, null));
            return Void.TYPE; 
        });
        sm.registerFunction("title.reset", (sc, in) -> 
        { 
            ((ServerPlayerEntity) in[0].get(sc)).connection.sendPacket(new STitlePacket(STitlePacket.Type.RESET, null));
            return Void.TYPE; 
        });
        sm.registerFunction("title.send", (sc, in) -> 
        { 
            ITextComponent text = new StringTextComponent(SnuviUtils.connect(sc, in, 1));
            ((ServerPlayerEntity) in[0].get(sc)).connection.sendPacket(new STitlePacket(STitlePacket.Type.TITLE, text));
            return Void.TYPE; 
        });
        sm.registerFunction("title.setsub", (sc, in) -> 
        { 
            ITextComponent text = new StringTextComponent(SnuviUtils.connect(sc, in, 1));
            ((ServerPlayerEntity) in[0].get(sc)).connection.sendPacket(new STitlePacket(STitlePacket.Type.SUBTITLE, text));
            return Void.TYPE; 
        });
       
        // ---------------------------------------------------------------------    
        // Player-library
        // ---------------------------------------------------------------------  
        sm.registerFunction("player.getitemamount", (sc, in) -> (double) InventoryUtils.searchInventoryFor(((PlayerEntity) in[0].get(sc)).inventory, (ItemStack) in[2].get(sc), in[1].getBoolean(sc)));                
        sm.registerFunction("player.removeitem", (sc, in) -> 
        { 
            ItemStack stack = ((ItemStack) in[1].get(sc)).copy();
            stack.setCount(InventoryUtils.removeFromInventory(((PlayerEntity) in[0].get(sc)).inventory, stack));
            return stack;
        });                 
        sm.registerFunction("player.giveitem", (sc, in) -> 
        {       
            ItemStack stack = ((ItemStack) in[1].get(sc)).copy();
            stack.setCount(InventoryUtils.addToInventory(((PlayerEntity) in[0].get(sc)).inventory, stack));
            return stack;
        });
        sm.registerFunction("player.shootprojectile", (sc, in) -> launchProjectile((PlayerEntity) in[0].get(sc), 
                getClass(in[1].getString(sc)), in[2].getDouble(sc), in.length >= 4 ? in[3].get(sc) : null));
        sm.registerFunction("player.respawn", (sc, in) -> 
        {
            final ServerPlayerEntity p = ((ServerPlayerEntity) in[0].get(sc));
            scheduler.scheduleTask(() ->
            {
                try
                {
                    p.connection.processClientStatus(new CClientStatusPacket(CClientStatusPacket.State.PERFORM_RESPAWN));
                }
                catch(ThreadQuickExitException ex)
                {
                    // Minecraft needs this for canceling and queueing into main thread
                }
            });
            return Void.TYPE; 
        });
        sm.registerFunction("player.clearinventory", (sc, in) -> 
        { 
            ((PlayerEntity) in[0].get(sc)).inventory.clear();
            return Void.TYPE; 
        });  
        sm.registerFunction("player.inventorytolist", (sc, in) -> ((PlayerEntity) in[1].get(sc)).inventory.mainInventory);    
        sm.registerFunction("player.getnearest", (sc, in) -> 
        {
            Location l = (Location) in[0].get(sc);
            return l.getWorld().getClosestPlayer(l.getX(), l.getY(), l.getZ(), -1, p -> true);
        }); 
        sm.registerFunction("player.say", (sc, in) -> 
        {
            try
            {
                ((ServerPlayerEntity) in[0].get(sc)).connection.processChatMessage(new CChatMessagePacket(SnuviUtils.connect(sc, in, 1)));
            }
            catch(ThreadQuickExitException ex)
            {
                // Minecraft needs this for canceling and queueing into main thread
            }
            return Void.TYPE; 
        });
        sm.registerFunction("player.speak", (sc, in) -> 
        { 
            sendMessageToGroup(server, scripts, perms, in[0].get(sc), sc, concat(sc, 2, "§7[§r" + in[1].getString(sc) + "§7]§r ", in)); 
            return Void.TYPE; 
        });
        sm.registerFunction("player.setcompass", (sc, in) -> 
        { 
            ((ServerPlayerEntity) in[0].get(sc)).connection.sendPacket(new SSpawnPositionPacket(((Location) in[1].get(sc)).getBlockPos())); 
            return Void.TYPE; 
        }); 
        sm.registerFunction("player.gethunger", (sc, in) -> (double) ((PlayerEntity) in[0].get(sc)).getFoodStats().getFoodLevel());
        sm.registerFunction("player.sethunger", (sc, in) -> 
        { 
            ((PlayerEntity) in[0].get(sc)).getFoodStats().setFoodLevel(in[1].getInt(sc)); 
            return Void.TYPE; 
        });
        sm.registerFunction("player.getsaturation", (sc, in) -> (double) ((PlayerEntity) in[0].get(sc)).getFoodStats().getSaturationLevel());
        sm.registerFunction("player.setsaturation", (sc, in) -> 
        { 
            ReflectionUtils.setSaturation(((PlayerEntity) in[0].get(sc)).getFoodStats(), in[1].getFloat(sc)); 
            return Void.TYPE; 
        });
        sm.registerFunction("player.getname", (sc, in) -> 
        { 
            Object o = in[0].get(sc);
            if(o instanceof PlayerEntity)
            {
                return ((PlayerEntity) o).getName().getFormattedText();
            }
            return server.getPlayerProfileCache().getProfileByUUID(getUUID(o.toString())).getName();
        });
        sm.registerFunction("player.getuuid", (sc, in) -> 
        { 
            Object o = in[0].get(sc);
            if(o instanceof PlayerEntity)
            {
                return ((PlayerEntity) o).getUniqueID();
            }
            return playerBank.getUUID(o.toString());
        });
        sm.registerFunction("player.getid", (sc, in) -> (double) getId(playerBank, in[0].get(sc)));
        sm.registerFunction("player.get", (sc, in) -> server.getPlayerList().getPlayerByUUID(getUUID(in[0].get(sc))));
        sm.registerFunction("player.getuuidfromid", (sc, in) -> playerBank.getUUIDfromID(in[0].getInt(sc)));
        sm.registerFunction("player.getnamefromid", (sc, in) -> playerBank.getNamefromID(in[0].getInt(sc)));
        sm.registerFunction("player.getip", (sc, in) -> ((ServerPlayerEntity) in[0].get(sc)).connection.netManager.getRemoteAddress().toString());
        sm.registerFunction("player.iscreative", (sc, in) -> ((PlayerEntity) in[0].get(sc)).isCreative());
        sm.registerFunction("player.isspectator", (sc, in) -> ((PlayerEntity) in[0].get(sc)).isSpectator());
        sm.registerFunction("player.issurvival", (sc, in) -> 
        { 
            PlayerEntity p = (PlayerEntity) in[0].get(sc);
            return !p.isCreative() && !p.isSpectator();
        });
        sm.registerFunction("player.isadventure", (sc, in) -> !((PlayerEntity) in[0].get(sc)).abilities.allowEdit);
        sm.registerFunction("player.setfly", (sc, in) -> 
        {
            PlayerEntity p = ((PlayerEntity) in[0].get(sc));
            boolean b = in[1].getBoolean(sc);
            p.abilities.allowFlying = b;
            p.abilities.isFlying = b;
            p.sendPlayerAbilities();
            return Void.TYPE;
        });
        sm.registerFunction("player.hasfly", (sc, in) -> ((PlayerEntity) in[0].get(sc)).abilities.allowFlying);
        sm.registerFunction("player.isflying", (sc, in) -> ((PlayerEntity) in[0].get(sc)).abilities.isFlying);
        sm.registerFunction("player.setgamemode", (sc, in) -> 
        { 
            PlayerEntity p = (PlayerEntity) in[0].get(sc);
            switch(in[1].get(sc).toString()) 
            {
                case "survival":
                case "s":
                case "0":  
                    p.setGameType(GameType.SURVIVAL);
                    break;
                case "creative":
                case "c":        
                case "1":  
                    p.setGameType(GameType.CREATIVE);
                    break;
                case "adventure":         
                case "a":         
                case "2":  
                    p.setGameType(GameType.ADVENTURE);
                    break;
                case "spectator":         
                case "w":         
                case "3":  
                    p.setGameType(GameType.SPECTATOR);
                    break;  
                default:
                    p.setGameType(GameType.CREATIVE);
                    break;
            }
            return Void.TYPE;
        });
        sm.registerFunction("player.getlastdamager", (sc, in) -> 
        { 
            DamageSource ds = ((PlayerEntity) in[0].get(sc)).getLastDamageSource();
            if(ds == null)
            {
                return null;
            }
            return ds.getImmediateSource();
        });
        sm.registerFunction("player.dropinventory", (sc, in) -> 
        { 
            ((PlayerEntity) in[0].get(sc)).inventory.dropAllItems();
            return Void.TYPE; 
        });
        sm.registerFunction("player.gettarget", (sc, in) -> 
        { 
            PlayerEntity p = (PlayerEntity) in[0].get(sc);
            
            double radius = in[1].getDouble(sc);
            if(radius > 128.0)
            {
                radius = 128.0;
            }
            
            RayTraceContext.BlockMode bm = RayTraceContext.BlockMode.OUTLINE;
            if(in.length >= 3 && in[2].getBoolean(sc))
            {
                bm = RayTraceContext.BlockMode.COLLIDER;
            }
            RayTraceContext.FluidMode fm = RayTraceContext.FluidMode.NONE;
            if(in.length >= 4 && in[3].getBoolean(sc))
            {
                fm = RayTraceContext.FluidMode.ANY;
            }
            
            Vec3d start = p.getEyePosition(0.0f);
            Vec3d unit = p.getLook(0.0f);
            Vec3d end = start.add(unit.x * radius, unit.y * radius, unit.z * radius);
            
            BlockRayTraceResult result = p.world.rayTraceBlocks(new RayTraceContext(start, end, bm, fm, p));
            
            if(result.getType() == RayTraceResult.Type.BLOCK) 
            {
                return new Location(p.world, result.getPos());
            }
            
            return new Location(p.world, end);
        });
                
        sm.registerFunction("player.gettargetentity", (sc, in) -> Utils.getTargetedEntity((PlayerEntity) in[0].get(sc), in[1].getDouble(sc), getClass(in[2].getString(sc))));
        sm.registerFunction("player.hasscript", (sc, in) -> scripts.isRegistered((PlayerEntity) in[0].get(sc)));
        sm.registerAlias("player.hasscript", "player.hasquest");
        sm.registerFunction("player.action", (sc, in) -> 
        {     
            StringTextComponent text = new StringTextComponent(SnuviUtils.connect(sc, in, 1));
            doForGroup(server, scripts, perms, in[0].get(sc), sc, p -> ((ServerPlayerEntity) p).sendStatusMessage(text, true));
            return Void.TYPE; 
        }); 
        sm.registerFunction("player.disconnect", (sc, in) -> 
        {
            ((ServerPlayerEntity) in[0].get(sc)).connection.disconnect(new StringTextComponent(in[1].getString(sc)));
            return Void.TYPE; 
        }); 
        sm.registerFunction("player.kick", (sc, in) -> scripts.unregisterPlayer(sc, (PlayerEntity) in[0].get(sc)));  
        sm.registerFunction("player.getspawn", (sc, in) -> 
        {   
            ServerWorld ws = (ServerWorld) in[1].get(sc);
            return new Location(ws, ((PlayerEntity) in[0].get(sc)).getBedLocation(ws.getDimension().getType()));
        });
        sm.registerAlias("player.getspawn", "player.getbedspawn");
        sm.registerFunction("player.setspawn", (sc, in) -> 
        {     
            Location l = (Location) in[1].get(sc);
            ((ServerPlayerEntity) in[0].get(sc)).setSpawnPoint(l.getBlockPos(), true, false, l.getWorld().getDimension().getType());
            return Void.TYPE; 
        });
        sm.registerAlias("player.setspawn", "player.setbedspawn");
        sm.registerFunction("player.damageitem", (sc, in) -> 
        {   
            PlayerEntity p = (PlayerEntity) in[0].get(sc);
            p.getHeldItemMainhand().damageItem(in[1].getInt(sc), p, (c) -> {}); 
            return Void.TYPE; 
        });
        sm.registerFunction("player.damagearmor", (sc, in) -> 
        {   
            ((PlayerEntity) in[0].get(sc)).inventory.damageArmor(in[1].getFloat(sc));
            return Void.TYPE; 
        });
        sm.registerFunction("player.openenderchest", (sc, in) -> 
        {   
            PlayerEntity p1 = (PlayerEntity) in[0].get(sc);
            PlayerEntity p2 = (PlayerEntity) in[1].get(sc);
            
            EnderChestInventory inv = p2.getInventoryEnderChest();
            p1.openContainer(new SimpleNamedContainerProvider((id, pInv, p) -> 
            {
               return ChestContainer.createGeneric9X3(id, pInv, inv);
            }, new StringTextComponent(in[2].getString(sc))));

            return Void.TYPE; 
        });
        sm.registerFunction("player.getlevel", (sc, in) -> (double) ((PlayerEntity) in[0].get(sc)).experienceLevel);
        sm.registerFunction("player.getexp", (sc, in) -> (double) ((PlayerEntity) in[0].get(sc)).experience);
        sm.registerFunction("player.addlevel", (sc, in) -> 
        {
            PlayerEntity p = (PlayerEntity) in[0].get(sc);
            p.addExperienceLevel(in[1].getInt(sc));
            return Void.TYPE; 
        });
        sm.registerFunction("player.setexp", (sc, in) -> 
        {
            PlayerEntity p = (PlayerEntity) in[0].get(sc);
            float f = in[1].getFloat(sc);
            if(f < 0.0f)
            {
                f = 0.0f;
            }
            else if(f > 1.0f)
            {
                f = 1.0f;
            }
            p.giveExperiencePoints((int) (f * p.xpBarCap()));
            return Void.TYPE; 
        });
        sm.registerFunction("player.gethead", (sc, in) -> 
        {
            ItemStack stack = new ItemStack(Items.PLAYER_HEAD);
            CompoundNBT com = stack.getOrCreateTag();
            GameProfile gp = new GameProfile(getUUID(in[0].get(sc)), in[1].getString(sc));
            gp = SkullTileEntity.updateGameProfile(gp);
            com.put("SkullOwner", NBTUtil.writeGameProfile(new CompoundNBT(), gp));
            return stack;
        });
        sm.registerFunction("player.setspeed", (sc, in) -> 
        {
            PlayerEntity p = (PlayerEntity) in[0].get(sc);
            float f = in[1].getFloat(sc);
            ReflectionUtils.setFlySpeed(p.abilities, f);
            ReflectionUtils.setWalkSpeed(p.abilities, f * 2);
            p.sendPlayerAbilities();
            return true;
        });
        sm.registerFunction("player.near", (sc, in) -> Utils.getPlayers((Entity) in[0].get(sc), in[1].getDouble(sc)));
        sm.registerFunction("player.getinvslot", (sc, in) -> ((PlayerEntity) in[0].get(sc)).inventory.mainInventory.get(in[1].getInt(sc)));
        sm.registerFunction("player.setinvslot", (sc, in) -> 
        {     
            ((PlayerEntity) in[0].get(sc)).inventory.mainInventory.set(in[1].getInt(sc), ((ItemStack) in[2].get(sc)).copy()); 
            return Void.TYPE;
        });
        sm.registerFunction("player.getenderslot", (sc, in) -> ((PlayerEntity) in[0].get(sc)).getInventoryEnderChest().getStackInSlot(in[1].getInt(sc)));
        sm.registerFunction("player.setenderslot", (sc, in) -> 
        {     
            ((PlayerEntity) in[0].get(sc)).getInventoryEnderChest().setInventorySlotContents(in[1].getInt(sc), ((ItemStack) in[2].get(sc)).copy()); 
            return Void.TYPE;
        });
        sm.registerFunction("player.setdisplayname", (sc, in) -> 
        {     
            ((ModEntityPlayerMP) in[0].get(sc)).setTabListDisplayName(in[1].getString(sc));
            return Void.TYPE;
        });

        // ---------------------------------------------------------------------    
        // Players-library
        // --------------------------------------------------------------------- 
        sm.registerFunction("players.getamount", (sc, in) -> (double) server.getCurrentPlayerCount());
        sm.registerFunction("players.tolist", (sc, in) -> new ArrayList(server.getPlayerList().getPlayers()));    
        sm.registerFunction("players.toworldlist", (sc, in) -> new ArrayList(((World) in[0].get(sc)).getPlayers()));
        sm.registerFunction("players.near", (sc, in) -> 
        {     
            Location l = (Location) in[0].get(sc);
            return Utils.getPlayers(l.getWorld(), l.getX(), l.getY(), l.getZ(), in[1].getDouble(sc)); 
        });
        
        // ---------------------------------------------------------------------    
        // World-library
        // ---------------------------------------------------------------------
        
        sm.registerAlias("players.toworldlist", "world.getplayers");
        sm.registerFunction("world.register", (sc, in) -> WorldManager.register(in[0].getString(sc)));
        sm.registerFunction("world.unregister", (sc, in) -> WorldManager.unregister(server, in[0].getString(sc)));
        sm.registerFunction("world.get", (sc, in) -> 
        {     
            return WorldManager.get(server, in[0].getString(sc));
        });
        sm.registerFunction("world.getname", (sc, in) -> 
        {     
            return WorldManager.getName((World) in[0].get(sc));
        });
        sm.registerFunction("world.setdiffi", (sc, in) -> 
        {           
            Difficulty diffi = Difficulty.valueOf(in[1].getString(sc).toUpperCase());
            ((World) in[0].get(sc)).getWorldInfo().setDifficulty(diffi);                                     
            return Void.TYPE;
        });  
        sm.registerFunction("world.setgamerule", (sc, in) -> 
        {                    
            Object o = ((World) in[0].get(sc)).getGameRules().get(Mapper.getGameRule(in[1].getString(sc)));
            if(o instanceof GameRules.BooleanValue)
            {
                ((GameRules.BooleanValue) o).set(in[2].getBoolean(sc), server);
            }
            if(o instanceof GameRules.IntegerValue)
            {
                ReflectionUtils.setIntegerValue((GameRules.IntegerValue) o, in[2].getInt(sc));
            }
            return Void.TYPE;
        });  
        sm.registerFunction("world.getgamerule", (sc, in) -> 
        {                    
            Object o = ((World) in[0].get(sc)).getGameRules().get(Mapper.getGameRule(in[1].getString(sc)));
            if(o instanceof GameRules.BooleanValue)
            {
                return ((GameRules.BooleanValue) o).get();
            }
            if(o instanceof GameRules.IntegerValue)
            {
                return (double) ((GameRules.IntegerValue) o).get();
            }
            return null;
        });  
        sm.registerFunction("world.setspawn", (sc, in) -> 
        {    
            Location l = ((Location) in[0].get(sc));
            l.getWorld().getDimension().setSpawnPoint(l.getBlockPos());
            return Void.TYPE;
        });
        sm.registerFunction("world.getspawn", (sc, in) -> 
        {    
            World w = (World) in[0].get(sc);
            return new Location(w, w.getSpawnPoint());
        });
        sm.registerFunction("world.getall", (sc, in) -> 
        {    
            ArrayList<World> worlds = new ArrayList<>();
            for(World w : server.getWorlds())
            {
                worlds.add(w);
            }
            return worlds;
        });
        sm.registerFunction("world.settime", (sc, in) -> 
        {    
            ((World) in[0].get(sc)).setDayTime(in[1].getLong(sc));
            return Void.TYPE;
        });
        sm.registerFunction("world.gettime", (sc, in) -> (double) ((World) in[0].get(sc)).getDayTime());
        sm.registerFunction("world.hasstorm", (sc, in) -> ((World) in[0].get(sc)).isRaining());
        sm.registerFunction("world.clearweather", (sc, in) -> 
        {    
            WorldInfo wi = ((World) in[0].get(sc)).getWorldInfo();
            wi.setClearWeatherTime(in[1].getInt(sc));
            wi.setRainTime(0);
            wi.setThunderTime(0);
            wi.setRaining(false);
            wi.setThundering(false);
            return Void.TYPE;
        });
        sm.registerFunction("world.setrain", (sc, in) -> 
        {    
            WorldInfo wi = ((World) in[0].get(sc)).getWorldInfo();
            wi.setClearWeatherTime(0);
            int i = in[1].getInt(sc);
            wi.setRainTime(i);
            wi.setThunderTime(i);
            wi.setRaining(true);
            wi.setThundering(false);
            return Void.TYPE;
        });
        sm.registerFunction("world.setthunder", (sc, in) -> 
        {    
            WorldInfo wi = ((World) in[0].get(sc)).getWorldInfo();
            wi.setClearWeatherTime(0);
            int i = in[1].getInt(sc);
            wi.setRainTime(i);
            wi.setThunderTime(i);
            wi.setRaining(true);
            wi.setThundering(true);
            return Void.TYPE;
        });
        
        // ---------------------------------------------------------------------    
        // Item-library   
        // ---------------------------------------------------------------------   
        sm.registerFunction("item.drop", (sc, in) -> 
        {       
            Location l = (Location) in[0].get(sc);
            World w = l.getWorld().getWorld();
            BlockPos pos = l.getBlockPos();
            ItemStack stack = ((ItemStack) in[1].get(sc)).copy();
            int amount = stack.getCount();
            while(amount > stack.getMaxStackSize())
            {            
                stack.setCount(stack.getMaxStackSize());
                amount -= stack.getMaxStackSize();
                Block.spawnAsEntity(w, pos, stack.copy());
            }
            if(amount > 0)
            {
                stack.setCount(amount);
                Block.spawnAsEntity(w, pos, stack);
            }
            return Void.TYPE; 
        });                  
        sm.registerFunction("item.gettag", (sc, in) -> ItemTags.getCollection().get(new ResourceLocation(in[0].getString(sc))));
        sm.registerFunction("item.hastag", (sc, in) -> ((Tag<Item>) in[0].get(sc)).contains(((ItemStack) in[1].get(sc)).getItem()));
        sm.registerFunction("item.get", (sc, in) -> ((ItemStack) in[0].get(sc)).getItem());
        sm.registerFunction("item.gettype", (sc, in) -> ((ItemStack) in[0].get(sc)).getItem().getRegistryName().toString());     
        sm.registerFunction("item.getamount", (sc, in) -> (double) ((ItemStack) in[0].get(sc)).getCount()); 
        sm.registerFunction("item.setamount", (sc, in) -> 
        {   
            ((ItemStack) in[0].get(sc)).setCount(in[1].getInt(sc));
            return Void.TYPE; 
        });   
        sm.registerFunction("item.getname", (sc, in) -> ((ItemStack) in[0].get(sc)).getDisplayName().getFormattedText());
        sm.registerFunction("item.setname", (sc, in) -> 
        {   
            ((ItemStack) in[0].get(sc)).setDisplayName(new StringTextComponent(SnuviUtils.connect(sc, in, 1))); 
            return Void.TYPE; 
        });
        sm.registerFunction("item.getlore", (sc, in) -> 
        {    
            return ItemStackUtils.getLore((ItemStack) in[0].get(sc)); 
        });
        sm.registerFunction("item.setlore", (sc, in) -> 
        {   
            ItemStackUtils.setLore((ItemStack) in[0].get(sc), (List<Object>) in[1].get(sc)); 
            return Void.TYPE; 
        });
        sm.registerFunction("item.addlore", (sc, in) -> 
        {   
            ItemStackUtils.addLore((ItemStack) in[0].get(sc), SnuviUtils.connect(sc, in, 2), in[1].getInt(sc)); 
            return Void.TYPE; 
        });
        sm.registerFunction("item.addenchant", (sc, in) -> 
        {
            ItemStack stack = (ItemStack) in[0].get(sc);
            Enchantment ench = Mapper.getEnchantment(in[1].getString(sc));
            int i = in[2].getInt(sc);
            stack.addEnchantment(ench, i);
            return true;
        });
        sm.registerFunction("item.getenchantlevel", (sc, in) -> (double) EnchantmentHelper.getEnchantmentLevel(Mapper.getEnchantment(in[1].getString(sc)), (ItemStack) in[0].get(sc)));
        sm.registerFunction("item.setcooldown", (sc, in) -> 
        {   
            ((PlayerEntity) in[0].get(sc)).getCooldownTracker().setCooldown(((ItemStack) in[1].get(sc)).getItem(), in[2].getInt(sc)); 
            return Void.TYPE; 
        });
        sm.registerFunction("item.addpotion", (sc, in) -> 
        { 
            ItemStack stack = (ItemStack) in[0].get(sc);
            List<EffectInstance> list = PotionUtils.getEffectsFromStack(stack);
            list.add(new EffectInstance(Mapper.getPotion(in[1].getString(sc)), in[2].getInt(sc), in[3].getInt(sc)));
            PotionUtils.appendEffects(stack, list);
            return Void.TYPE;
        });
        sm.registerFunction("item.addflag", (sc, in) -> 
        { 
            ItemStackUtils.addItemFlag((ItemStack) in[0].get(sc), ItemStackUtils.ItemFlag.valueOf(in[1].getString(sc).toUpperCase()));
            return Void.TYPE;
        });
        sm.registerFunction("item.addattribute", (sc, in) -> 
        { 
            ItemStackUtils.addAttribute((ItemStack) in[0].get(sc), 
                    ItemStackUtils.Attribute.valueOf(in[1].getString(sc).toUpperCase()), 
                    EquipmentSlotType.valueOf(in[2].getString(sc).toUpperCase()), 
                    in[3].getDouble(sc), 
                    Operation.valueOf(in[4].getString(sc).toUpperCase()));
            return Void.TYPE;
        });
        sm.registerFunction("item.setmagic", (sc, in) -> 
        { 
            ItemStack stack = (ItemStack) in[0].get(sc);                  
            int value = in[1].getInt(sc);  
            if(stack.getItem() instanceof ArmorItem)
            {
                CompoundNBT com = stack.getTag();
                if(com == null)
                {
                    com = new CompoundNBT();
                    stack.setTag(com);
                }
                com.putInt("magic", value);
            }
            else
            {
                throw new IllegalArgumentException("magic resistance is only for armor");
            }
            return Void.TYPE;
        });
        sm.registerFunction("item.clone", (sc, in) -> ((ItemStack) in[0].get(sc)).copy());

        // ---------------------------------------------------------------------    
        // Location-library
        // ---------------------------------------------------------------------  
        sm.registerFunction("loc.new", (sc, in) -> 
        { 
            if(in.length >= 6)
            {
                return new Location((World) in[0].get(sc), 
                        in[1].getDouble(sc), in[2].getDouble(sc), in[3].getDouble(sc), 
                        in[4].getFloat(sc), in[5].getFloat(sc));
            }
            return new Location((World) in[0].get(sc), in[1].getDouble(sc), in[2].getDouble(sc), in[3].getDouble(sc), 0, 0);
        });   
        sm.registerFunction("loc.getx", (sc, in) -> ((Location) in[0].get(sc)).getX());  
        sm.registerFunction("loc.gety", (sc, in) -> ((Location) in[0].get(sc)).getY());  
        sm.registerFunction("loc.getz", (sc, in) -> ((Location) in[0].get(sc)).getZ());  
        sm.registerFunction("loc.setx", (sc, in) -> 
        { 
            ((Location) in[0].get(sc)).setX(in[1].getDouble(sc)); 
            return Void.TYPE; 
        });  
        sm.registerFunction("loc.sety", (sc, in) -> 
        { 
            ((Location) in[0].get(sc)).setY(in[1].getDouble(sc)); 
            return Void.TYPE; 
        });  
        sm.registerFunction("loc.setz", (sc, in) -> 
        { 
            ((Location) in[0].get(sc)).setZ(in[1].getDouble(sc)); 
            return Void.TYPE; 
        });  
        sm.registerFunction("loc.addx", (sc, in) -> 
        { 
            ((Location) in[0].get(sc)).addX(in[1].getDouble(sc)); 
            return Void.TYPE; 
        });  
        sm.registerFunction("loc.addy", (sc, in) -> 
        { 
            ((Location) in[0].get(sc)).addY(in[1].getDouble(sc)); 
            return Void.TYPE; 
        });  
        sm.registerFunction("loc.addz", (sc, in) -> 
        { 
            ((Location) in[0].get(sc)).addZ(in[1].getDouble(sc)); 
            return Void.TYPE; 
        });  
        sm.registerFunction("loc.setyaw", (sc, in) -> 
        { 
            ((Location) in[0].get(sc)).setYaw(in[1].getFloat(sc)); 
            return Void.TYPE; 
        });  
        sm.registerFunction("loc.getyaw", (sc, in) -> (double) ((Location) in[0].get(sc)).getYaw());  
        sm.registerFunction("loc.setpitch", (sc, in) -> 
        { 
            ((Location) in[0].get(sc)).setPitch(in[1].getFloat(sc)); 
            return Void.TYPE;
        });  
        sm.registerFunction("loc.getpitch", (sc, in) -> (double) ((Location) in[0].get(sc)).getPitch());
        sm.registerFunction("loc.getworld", (sc, in) -> ((Location) in[0].get(sc)).getWorld());  
        sm.registerFunction("loc.distance", (sc, in) -> ((Location) in[0].get(sc)).getPos().distanceTo(((Location) in[1].get(sc)).getPos()));  
        sm.registerFunction("loc.mod", (sc, in) -> ((Location) in[0].get(sc)).copyAdd(in[1].getDouble(sc), in[2].getDouble(sc), in[3].getDouble(sc)));
        sm.registerFunction("loc.getcoord", (sc, in) -> 
        {
            Location l = (Location) in[0].get(sc);
            switch(in[1].getString(sc))
            {
                case "x":
                    return l.getX();
                case "y":
                    return l.getY();
                case "z":
                    return l.getZ();
                case "bx":
                    return (double) MathHelper.floor(l.getX());
                case "by":
                    return (double) MathHelper.floor(l.getY());
                case "bz":
                    return (double) MathHelper.floor(l.getZ());
                case "w":
                    return WorldManager.getName(l.getWorld());
                default:
                    return null;
            }
        });
        sm.registerFunction("loc.isbetween", (sc, in) -> 
        { 
            Location l1 = (Location) in[0].get(sc);
            Location l2 = (Location) in[1].get(sc);
            Location l3 = (Location) in[2].get(sc);
            return l1.getX() >= Math.min(l2.getX(), l3.getX()) && l1.getX() <= Math.max(l2.getX(), l3.getX()) &&
                    l1.getY() >= Math.min(l2.getY(), l3.getY()) && l1.getY() <= Math.max(l2.getY(), l3.getY()) &&
                    l1.getZ() >= Math.min(l2.getZ(), l3.getZ()) && l1.getZ() <= Math.max(l2.getZ(), l3.getZ());
        });
        sm.registerFunction("loc.sort", (sc, in) -> 
        { 
            Location l1 = (Location) in[0].get(sc);
            Location l2 = (Location) in[1].get(sc);
            
            if(l1.getX() > l2.getX())
            {
                double tmp = l1.getX();
                l1.setX(l2.getX());
                l2.setX(tmp);
            }
            
            if(l1.getY() > l2.getY())
            {
                double tmp = l1.getY();
                l1.setY(l2.getY());
                l2.setY(tmp);
            }
            
            if(l1.getZ() > l2.getZ())
            {
                double tmp = l1.getZ();
                l1.setZ(l2.getZ());
                l2.setZ(tmp);
            }
            
            return Void.TYPE;
        });

        // ---------------------------------------------------------------------    
        // Block-library
        // ---------------------------------------------------------------------    
        sm.registerFunction("block.gettag", (sc, in) -> BlockTags.getCollection().get(new ResourceLocation(in[0].getString(sc))));
        sm.registerFunction("block.hastag", (sc, in) -> ((Tag<Block>) in[0].get(sc)).contains((Block) in[1].get(sc)));
        sm.registerFunction("block.gettype", (sc, in) -> 
        {
            Location l = (Location) in[0].get(sc);
            return l.getWorld().getBlockState(l.getBlockPos()).getBlock().getRegistryName().toString();
        });
        sm.registerFunction("block.get", (sc, in) -> 
        {
            Location l = (Location) in[0].get(sc);
            return l.getWorld().getBlockState(l.getBlockPos()).getBlock();
        });
        sm.registerFunction("block.getproperty", (sc, in) -> Mapper.getProperty(in[0].getString(sc)));
        sm.registerFunction("block.getstate", (sc, in) -> 
        {
            Location l = (Location) in[0].get(sc);
            IProperty prop = (IProperty) in[1].get(sc);
            BlockState state = l.getWorld().getBlockState(l.getBlockPos());
            if(state.has(prop))
            {
                Object o =  l.getWorld().getBlockState(l.getBlockPos()).get(prop);
                if(o instanceof Number)
                {
                    return ((Number) o).doubleValue();
                }
                else if(o instanceof Boolean)
                {
                    return o;
                }
                return o.toString();
            }
            return null;
        });
        sm.registerFunction("block.clone", (sc, in) -> 
        { 
            Location l0 = (Location) in[0].get(sc);
            Location l1 = (Location) in[1].get(sc);
            
            IWorld w0 = l0.getWorld();
            BlockPos pos0 = l0.getBlockPos();
            BlockState state = w0.getBlockState(pos0);
            TileEntity tileEnt0 = w0.getTileEntity(pos0);
                    
            IWorld w1 = l1.getWorld();
            BlockPos pos1 = l1.getBlockPos();
            w1.setBlockState(pos1, state, 2);
            TileEntity tileEnt1 = w1.getTileEntity(pos1);
            if(tileEnt0 != null && tileEnt1 != null)
            {
                CompoundNBT nbt = tileEnt0.write(new CompoundNBT());
                nbt.putInt("x", pos1.getX());
                nbt.putInt("y", pos1.getY());
                nbt.putInt("z", pos1.getZ());
                tileEnt1.read(nbt);
                tileEnt1.markDirty();
            }
            return Void.TYPE; 
        });
        sm.registerFunction("block.set", (sc, in) -> 
        { 
            Location l = (Location) in[0].get(sc);
            BlockStateParser parser = new BlockStateParser(new StringReader(in[1].getString(sc)), true);
            BlockState state = parser.parse(true).getState();
            l.getWorld().setBlockState(l.getBlockPos(), state, 2);
            return Void.TYPE; 
        });
        sm.registerFunction("block.setsign", (sc, in) -> 
        { 
            Location l = (Location) in[0].get(sc);
            SignTileEntity sign = (SignTileEntity) l.getWorld().getTileEntity(l.getBlockPos());
            sign.signText[in[1].getInt(sc)] = new StringTextComponent(SnuviUtils.connect(sc, in, 2));

            SUpdateTileEntityPacket packet = sign.getUpdatePacket();
            World w = sign.getWorld();
            if(w != null)
            {
                w.getPlayers().stream().filter(p -> p instanceof ServerPlayerEntity)
                        .forEach(p -> ((ServerPlayerEntity) p).connection.sendPacket(packet));
            }
            return Void.TYPE;
        });
        sm.registerFunction("block.getsign", (sc, in) -> 
        { 
            Location l = (Location) in[0].get(sc);
            SignTileEntity sign = (SignTileEntity) l.getWorld().getTileEntity(l.getBlockPos());
            return sign.signText[in[1].getInt(sc)].getString();
        });
        sm.registerFunction("block.setdoorstatus", (sc, in) -> 
        { 
            Location l = (Location) in[0].get(sc);
            BlockPos pos = l.getBlockPos();
            ((DoorBlock) l.getWorld().getBlockState(pos).getBlock()).toggleDoor(l.getWorld().getWorld(), pos, in[1].getBoolean(sc));
            return Void.TYPE; 
        });
        sm.registerFunction("block.getdoorstatus", (sc, in) -> 
        { 
            Location l = (Location) in[0].get(sc);
            return l.getBlockState().get(DoorBlock.OPEN);
        });
        sm.registerFunction("block.isdoor", (sc, in) -> 
        { 
            Location l = (Location) in[0].get(sc);
            return l.getWorld().getBlockState(l.getBlockPos()).getBlock() instanceof DoorBlock;
        });
        sm.registerFunction("block.issolid", (sc, in) -> 
        { 
            BlockState state = getBlockState((Location) in[0].get(sc));
            return state.isSolid(); 
        });
        sm.registerFunction("block.tostack", (sc, in) -> 
        { 
            Location l = (Location) in[0].get(sc);
            return new ItemStack(l.getBlockState().getBlock().asItem());
        });
        sm.registerFunction("block.getitemamount", (sc, in) -> 
        { 
            Location l = (Location) in[0].get(sc);
            TileEntity te = l.getWorld().getTileEntity(l.getBlockPos());
            if(te == null || !(te instanceof ChestTileEntity))
            {
                return 0.0d;
            }        
            return (double) InventoryUtils.searchInventoryFor((ChestTileEntity) te, (ItemStack) in[2].get(sc), in[1].getBoolean(sc));
        });
        sm.registerFunction("block.getsecchest", (sc, in) -> 
        { 
            Location l = (Location) in[0].get(sc);
            BlockPos pos = l.getBlockPos();
            BlockState state = l.getWorld().getBlockState(pos);
            ChestType chesttype = state.get(ChestBlock.TYPE);
            if(chesttype == ChestType.SINGLE)
            {
                return null;
            }
            Direction dir = ChestBlock.getDirectionToAttached(state);
            return l.copyAdd(dir.getXOffset(), dir.getYOffset(), dir.getZOffset());
        });
        sm.registerFunction("block.additem", (sc, in) -> 
        { 
            Location l = (Location) in[0].get(sc);
            ItemStack stack = ((ItemStack) in[1].get(sc));
            TileEntity te = l.getWorld().getTileEntity(l.getBlockPos());
            if(te == null || !(te instanceof ChestTileEntity))
            {
                return stack;
            }        
            stack.setCount(InventoryUtils.addToInventory((ChestTileEntity) te, stack));  
            return stack;
        });
        sm.registerFunction("block.subitem", (sc, in) -> 
        { 
            Location l = (Location) in[0].get(sc);
            ItemStack stack = ((ItemStack) in[1].get(sc));
            TileEntity te = l.getWorld().getTileEntity(l.getBlockPos());
            if(te == null || !(te instanceof ChestTileEntity))
            {
                return stack;
            }        
            stack.setCount(InventoryUtils.removeFromInventory((ChestTileEntity) te, stack));  
            return stack;
        });  
        sm.registerFunction("block.grow", (sc, in) ->
        {
            Location l1 = (Location) in[0].get(sc);
            World w = l1.getWorld().getWorld();
            BlockPos pos1 = l1.getBlockPos();
            BlockPos pos2 = ((Location) in[1].get(sc)).getBlockPos();

            int x = Math.min(pos1.getX(), pos2.getX());
            int endX = Math.max(pos1.getX(), pos2.getX());

            int y = Math.min(pos1.getY(), pos2.getY());
            int endY = Math.max(pos1.getY(), pos2.getY());

            int z = Math.min(pos1.getZ(), pos2.getZ());
            int endZ = Math.max(pos1.getZ(), pos2.getZ());

            if(endX - x > 50 || endY - y > 50 || endZ - z > 50)
            {
                throw new IllegalArgumentException("uhh, that area seems way to big for growing plants");
            }

            BlockPos relative;
            BlockState state;
            for(; x <= endX; x++)
            {
                for(; y <= endY; y++)
                {
                    for(; z <= endZ; z++)
                    {
                        relative = new BlockPos(x, y, z);
                        state = w.getBlockState(relative);
                        if(state.getBlock() instanceof CropsBlock)
                        {
                            w.setBlockState(relative, state.with(CropsBlock.AGE, 7));                
                        }                      
                    }
                }
            }
            return Void.TYPE;
        });  
        sm.registerFunction("block.setspawnertype", (sc, in) ->
        {
            Location l = (Location) in[0].get(sc);
            MobSpawnerTileEntity spawner = (MobSpawnerTileEntity) l.getWorld().getTileEntity(l.getBlockPos());
            spawner.getSpawnerBaseLogic().setEntityType(EntityType.byKey(in[1].getString(sc)).get());
            return Void.TYPE;
        });  

        // ---------------------------------------------------------------------    
        // Event-library 
        // ---------------------------------------------------------------------  
        sm.registerFunction("event.addmovedata", (sc, in) -> 
        {
            UUID uuid = in.length >= 5 ? getUUID(in[4].get(sc)) : null;
            PlayerMoveData pmd = new PlayerMoveData(sc, (Location) in[0].get(sc), (Location) in[1].get(sc), in[2].getInt(sc), in[3].getInt(sc), uuid);
            return (double) cec.registerMoveData(pmd);
        });
        sm.registerFunction("event.removemovedata", (sc, in) -> 
        { 
            cec.removeMoveData(in[0].getInt(sc));
            return Void.TYPE; 
        });
        
        // ---------------------------------------------------------------------    
        // damage stuff
        // ---------------------------------------------------------------------  
        sm.registerFunction("damage.getimmediatesource", (sc, in) -> ((DamageSource) in[0].get(sc)).getImmediateSource());   
        sm.registerFunction("damage.gettruesource", (sc, in) -> ((DamageSource) in[0].get(sc)).getTrueSource());   
        sm.registerFunction("damage.iscreativeplayer", (sc, in) -> ((DamageSource) in[0].get(sc)).isCreativePlayer()); 
        sm.registerFunction("damage.isabsolute", (sc, in) -> ((DamageSource) in[0].get(sc)).isDamageAbsolute()); 
        sm.registerFunction("damage.isdifficultyscaled", (sc, in) -> ((DamageSource) in[0].get(sc)).isDifficultyScaled()); 
        sm.registerFunction("damage.isexplosion", (sc, in) -> ((DamageSource) in[0].get(sc)).isExplosion()); 
        sm.registerFunction("damage.isfireDamage", (sc, in) -> ((DamageSource) in[0].get(sc)).isFireDamage()); 
        sm.registerFunction("damage.isunblockable", (sc, in) -> ((DamageSource) in[0].get(sc)).isMagicDamage()); 
        sm.registerFunction("damage.ismagic", (sc, in) -> ((DamageSource) in[0].get(sc)).isProjectile()); 
        sm.registerFunction("damage.isunblockable", (sc, in) -> ((DamageSource) in[0].get(sc)).isUnblockable()); 
        sm.registerFunction("damage.gettype", (sc, in) -> ((DamageSource) in[0].get(sc)).getDamageType()); 
        sm.registerFunction("damage.get", (sc, in) -> 
        { 
            Object o = in[0].get(sc);
            if(o instanceof LivingEntity)
            {
                LivingEntity ent = (LivingEntity) o;
                DamageSource ds;
                if(ent instanceof PlayerEntity)
                {
                    ds = DamageSource.causePlayerDamage((PlayerEntity) ent);
                }
                else
                {
                    ds = DamageSource.causeMobDamage(ent);
                }

                if(in[1].getBoolean(sc))
                {
                    ds.setDamageAllowedInCreativeMode();
                }
                if(in[2].getBoolean(sc))
                {
                    ds.setDamageBypassesArmor();
                }
                if(in[3].getBoolean(sc))
                {
                    ds.setDamageIsAbsolute();
                }
                if(in[4].getBoolean(sc))
                {
                    ds.setExplosion();
                }
                if(in[5].getBoolean(sc))
                {
                    ds.setFireDamage();
                }
                if(in[6].getBoolean(sc))
                {
                    ds.setMagicDamage();
                }
                if(in[7].getBoolean(sc))
                {
                   ds.setProjectile();
                } 
                return ds;
            }

            switch(o.toString())
            {
                case "inFire": return DamageSource.IN_FIRE;
                case "lightningBolt": return DamageSource.LIGHTNING_BOLT;
                case "onFire": return DamageSource.ON_FIRE;
                case "lava": return DamageSource.LAVA;
                case "hotFloor": return DamageSource.HOT_FLOOR;
                case "inWall": return DamageSource.IN_WALL;
                case "cramming": return DamageSource.CRAMMING;
                case "drown": return DamageSource.DROWN;
                case "starve": return DamageSource.STARVE;
                case "cactus": return DamageSource.CACTUS;
                case "fall": return DamageSource.FALL;
                case "flyIntoWall": return DamageSource.FLY_INTO_WALL;
                case "outOfWorld": return DamageSource.OUT_OF_WORLD;
                case "generic": return DamageSource.GENERIC;
                case "magic": return DamageSource.MAGIC;
                case "wither": return DamageSource.WITHER;
                case "anvil": return DamageSource.ANVIL;
                case "fallingBlock": return DamageSource.FALLING_BLOCK;
                case "dragonBreath": return DamageSource.DRAGON_BREATH;
                case "fireworks": return DamageSource.FIREWORKS;
                case "dryout": return DamageSource.DRYOUT;
                case "sweetBerryBush": return DamageSource.SWEET_BERRY_BUSH;
                case "thorns": return DamageSource.causeThornsDamage((Entity) in[1].get(sc));
            }
            return DamageSource.GENERIC;
        });

        // ---------------------------------------------------------------------    
        // entity commands
        // ---------------------------------------------------------------------  
        sm.registerFunction("entity.getarmorthoughness", (sc, in) -> ((LivingEntity) in[0].get(sc)).getAttribute(SharedMonsterAttributes.ARMOR_TOUGHNESS).getValue());
        sm.registerFunction("entity.getarmor", (sc, in) -> (double) ((LivingEntity) in[0].get(sc)).getTotalArmorValue()); 
        sm.registerFunction("entity.getmagicarmor", (sc, in) -> 
        {
            int level = 0;
            for(ItemStack stack : ((LivingEntity) in[0].get(sc)).getArmorInventoryList())
            {
                CompoundNBT com = stack.getTag();
                if(com != null && com.contains("magic"))
                {
                    level += com.getInt("magic");
                }
            }
            return level;
        }); 
        sm.registerFunction("entity.getenchantmentmodifier", (sc, in) -> EnchantmentHelper.getEnchantmentModifierDamage(((LivingEntity) in[0].get(sc)).getArmorInventoryList(), (DamageSource) in[1].get(sc)));
        sm.registerFunction("entity.setburning", (sc, in) -> 
        {
            ((Entity) in[0].get(sc)).setFire(in[1].getInt(sc));
            return Void.TYPE; 
        });  
        sm.registerFunction("entity.isburning", (sc, in) -> ((Entity) in[0].get(sc)).isBurning());  
        sm.registerFunction("entity.getlook", (sc, in) -> 
        {
            Object[] o = new Object[3];
            Vec3d v = ((Entity) in[0].get(sc)).getLookVec();
            o[0] = v.x;
            o[1] = v.y;
            o[2] = v.z;
            return o;
        });  
        sm.registerFunction("entity.getlocation", (sc, in) -> new Location((Entity) in[0].get(sc)));   
        sm.registerFunction("entity.damage", (sc, in) -> 
        { 
            if(in.length >= 3)
            {
                ((LivingEntity) in[0].get(sc)).attackEntityFrom((DamageSource) in[2].get(sc), in[1].getFloat(sc));
                return Void.TYPE; 
            }
            ((LivingEntity) in[0].get(sc)).attackEntityFrom(DamageSource.GENERIC, in[1].getFloat(sc));
            return Void.TYPE; 
        }); 
        sm.registerFunction("entity.damagedirect", (sc, in) -> 
        { 
            LivingEntity liv = (LivingEntity) in[0].get(sc);
            float damageAmount = in[1].getFloat(sc);
            DamageSource ds = (DamageSource) in[2].get(sc);
            if(!liv.isInvulnerableTo(ds))
            {
                float f2 = Math.max(damageAmount - liv.getAbsorptionAmount(), 0.0F);
                liv.setAbsorptionAmount(liv.getAbsorptionAmount() - (damageAmount - f2));
                float absorbedDamage = damageAmount - f2;
                if(absorbedDamage > 0.0f && absorbedDamage < 3.4028235E37f && ds.getTrueSource() instanceof ServerPlayerEntity)
                {
                    ((ServerPlayerEntity) ds.getTrueSource()).addStat(Stats.DAMAGE_DEALT_ABSORBED, Math.round(absorbedDamage * 10.0f));
                }

                if(f2 > 0.0f)
                {
                    float f1 = liv.getHealth();
                    liv.getCombatTracker().trackDamage(ds, f1, f2);
                    liv.setHealth(f1 - f2);
                    liv.setAbsorptionAmount(liv.getAbsorptionAmount() - f2);
                }
            }
            return Void.TYPE; 
        });
        sm.registerFunction("entity.fromsource", (sc, in) -> 
        { 
            DamageSource ds = (DamageSource) in[0].get(sc);
            Entity ent = ds.getTrueSource();
            if(ent == null)
            {
                return ds.getImmediateSource();
            }
            return ent;
        });
        sm.registerAlias("damage.get", "entity.getdamagesource");
        sm.registerFunction("entity.gethealth", (sc, in) -> (double) ((LivingEntity) in[0].get(sc)).getHealth());     
        sm.registerFunction("entity.sethealth", (sc, in) -> 
        { 
            ((LivingEntity) in[0].get(sc)).setHealth(in[1].getFloat(sc)); 
            return Void.TYPE; 
        });  
        sm.registerFunction("entity.setname", (sc, in) -> 
        { 
            Entity ent = (Entity) in[0].get(sc);
            ent.setCustomName(new StringTextComponent(in[1].getString(sc)));
            if(in.length >= 3)
            {
                ent.setCustomNameVisible(in[2].getBoolean(sc));   
                return Void.TYPE; 
            }
            ent.setCustomNameVisible(false);  
            return Void.TYPE; 
        }); 
        sm.registerFunction("entity.getname", (sc, in) -> ((Entity) in[0].get(sc)).getDisplayName().getFormattedText()); 
        sm.registerFunction("entity.throw", (sc, in) -> 
        { 
            Entity ent = (Entity) in[0].get(sc);
            ent.setMotion(in[1].getDouble(sc), in[2].getDouble(sc), in[3].getDouble(sc));
            ent.velocityChanged = true;
            return Void.TYPE; 
        });
        sm.registerFunction("entity.teleport", (sc, in) -> 
        { 
            Entity ent = (Entity) in[0].get(sc);
            Location l = (Location) in[1].get(sc); 
            if(l.getWorld() == null)
            {
                throw new IllegalArgumentException("world must not be null");
            }
            if(ent instanceof ServerPlayerEntity)
            {
                ServerPlayerEntity p = (ServerPlayerEntity) ent;

                p.stopRiding();
                if(p.isSleeping())
                {
                    p.func_225652_a_(true, true);
                }

                float yaw = l.getYaw() != 0.0f ? l.getYaw() : ent.rotationYaw;
                float pitch = l.getPitch() != 0.0f ? l.getPitch() : ent.rotationPitch;
                p.teleport((ServerWorld) l.getWorld(), l.getX(), l.getY(), l.getZ(), yaw, pitch);
            }
            else
            {
                if(ent.world != l.getWorld())
                {
                    ServerWorld ws = (ServerWorld) l.getWorld();
                    ent.changeDimension(ws.getDimension().getType());
                }
                if(l.getYaw() != 0 && l.getPitch() != 0)
                {
                    ent.setLocationAndAngles(l.getX(), l.getY(), l.getZ(), l.getYaw(), l.getPitch());
                }
                else
                {
                    ent.setLocationAndAngles(l.getX(), l.getY(), l.getZ(), ent.rotationYaw, ent.rotationPitch);
                }
            }
            
            return Void.TYPE; 
        });  
        sm.registerFunction("entity.setequip", (sc, in) -> 
        { 
            LivingEntity liv = (LivingEntity) in[0].get(sc);  
            ItemStack stack = ((ItemStack) in[2].get(sc)).copy();
            switch(in[1].getString(sc))
            {
                case "hand":
                    liv.setItemStackToSlot(EquipmentSlotType.MAINHAND, stack);
                    return Void.TYPE;
                case "head":
                    liv.setItemStackToSlot(EquipmentSlotType.HEAD, stack);
                    return Void.TYPE;
                case "chest":
                    liv.setItemStackToSlot(EquipmentSlotType.CHEST, stack);
                    return Void.TYPE;
                case "legs":
                    liv.setItemStackToSlot(EquipmentSlotType.LEGS, stack);
                    return Void.TYPE;
                case "feet":
                    liv.setItemStackToSlot(EquipmentSlotType.FEET, stack);
                    return Void.TYPE;
                case "offhand":
                    liv.setItemStackToSlot(EquipmentSlotType.OFFHAND, stack);
            }
            return Void.TYPE; 
        }); 
        sm.registerFunction("entity.getequip", (sc, in) -> 
        { 
            LivingEntity liv = (LivingEntity) in[0].get(sc);        
            switch(in[1].getString(sc))
            {
                case "hand":
                    return liv.getItemStackFromSlot(EquipmentSlotType.MAINHAND);
                case "head":
                    return liv.getItemStackFromSlot(EquipmentSlotType.HEAD);
                case "chest":
                    return liv.getItemStackFromSlot(EquipmentSlotType.CHEST);
                case "legs":
                    return liv.getItemStackFromSlot(EquipmentSlotType.LEGS);
                case "feet":
                    return liv.getItemStackFromSlot(EquipmentSlotType.FEET);
                case "offhand":
                    return liv.getItemStackFromSlot(EquipmentSlotType.OFFHAND);
            }
            return ItemStack.EMPTY;
        });
        sm.registerFunction("entity.removeall", (sc, in) -> 
        { 
            Class<? extends Entity> c = (Class<? extends Entity>) getClass(in[0].getString(sc));
            if(c == Entity.class)
            {
                return Void.TYPE; 
            }
            Location l = (Location) in[1].get(sc);
            Utils.getEntities(l.getWorld(), l.getX(), l.getY(), l.getZ(), in[2].getDouble(sc), c).stream().forEach(ent -> 
            {
                ent.remove();
            });
            return Void.TYPE; 
        });
        sm.registerFunction("entity.remove", (sc, in) -> 
        { 
            ((Entity) in[0].get(sc)).remove(); 
            return Void.TYPE; 
        });
        sm.registerFunction("entity.setinvulnerable", (sc, in) -> 
        { 
            ((Entity) in[0].get(sc)).setInvulnerable(in[1].getBoolean(sc)); 
            return Void.TYPE;
        });
        sm.registerFunction("entity.setsilent", (sc, in) -> 
        { 
            ((Entity) in[0].get(sc)).setSilent(in[1].getBoolean(sc)); 
            return Void.TYPE; 
        });
        sm.registerFunction("entity.setinvisible", (sc, in) -> 
        { 
            ((Entity) in[0].get(sc)).setInvisible(in[1].getBoolean(sc)); 
            return Void.TYPE; 
        });
        sm.registerFunction("entity.ride", (sc, in) -> 
        { 
            ((Entity) in[0].get(sc)).startRiding(((Entity) in[1].get(sc))); 
            return Void.TYPE; 
        });
        sm.registerFunction("entity.addeffect", (sc, in) -> 
        { 
            LivingEntity base = (LivingEntity) in[0].get(sc);
            Effect potion = Mapper.getPotion(in[1].getString(sc));
            if(potion == null) // doing this only to prevent EffectInstance doing shit
            {
                throw new IllegalArgumentException("potion does not exist");
            }
            if(base.isPotionActive(potion))
            {
                base.removePotionEffect(potion);
            }
            base.addPotionEffect(new EffectInstance(potion, in[2].getInt(sc), in[3].getInt(sc)));
            return Void.TYPE; 
        });
        sm.registerFunction("entity.cleareffects", (sc, in) -> 
        { 
            ((LivingEntity) in[0].get(sc)).clearActivePotions();
            return Void.TYPE; 
        });
        sm.registerFunction("entity.geteffectamplifier", (sc, in) -> 
        {
            EffectInstance effect = ((LivingEntity) in[0].get(sc)).getActivePotionEffect(Mapper.getPotion(in[1].getString(sc)));
            return effect == null ? 0 : effect.getAmplifier() + 1;
        });
        sm.registerFunction("entity.explode", (sc, in) -> 
        { 
            ((CreeperEntity) in[0].get(sc)).ignite(); 
            return Void.TYPE; 
        });
        sm.registerFunction("entity.spawnitemframe", (sc, in) -> 
        { 
            Location l = ((Location) in[0].get(sc));
            ItemFrameEntity frame = new ItemFrameEntity(l.getWorld().getWorld(), l.getBlockPos(), Direction.byName(in[1].getString(sc)));
            frame.setDisplayedItem(((ItemStack) in[2].get(sc))); // copy happens in internals
            l.getWorld().addEntity(frame);
            return Void.TYPE; 
        });
        sm.registerFunction("entity.getitemframe", (sc, in) -> ((ItemFrameEntity) in[0].get(sc)).getDisplayedItem());
        sm.registerFunction("entity.get", (sc, in) -> 
        { 
            Location l = (Location) in[0].get(sc);
            return Utils.getEntity(l.getWorld(), l.getX(), l.getY(), l.getZ(), in[1].getDouble(sc), getClass(in[2].getString(sc)));
        });
        sm.registerFunction("entity.getpotiontype", (sc, in) -> PotionUtils.getPotionFromItem(((PotionEntity) in[0].get(sc)).getItem()).getRegistryName().toString());
        sm.registerFunction("entity.setgravity", (sc, in) -> 
        { 
            ((Entity) in[0].get(sc)).setNoGravity(!in[1].getBoolean(sc)); 
            return Void.TYPE; 
        });
        sm.registerFunction("entity.iswet", (sc, in) -> ((Entity) in[0].get(sc)).isWet());
        sm.registerFunction("entity.setpickupdelay", (sc, in) -> 
        { 
            ((ItemEntity) in[0].get(sc)).setPickupDelay(in[1].getInt(sc)); 
            return Void.TYPE; 
        });
        sm.registerFunction("entity.setage", (sc, in) -> 
        { 
            ReflectionUtils.setAge((ItemEntity) in[0].get(sc), in[1].getInt(sc)); 
            return Void.TYPE; 
        });
        sm.registerFunction("entity.spawn", (sc, in) -> 
        { 
            ResourceLocation rl = new ResourceLocation(in[0].getString(sc));
            Location l = (Location) in[1].get(sc);
            ServerWorld sw = (ServerWorld) l.getWorld();

            CompoundNBT compoundnbt = in.length >= 3 ? JsonToNBT.getTagFromJson(in[2].getString(sc)) : new CompoundNBT(); 
            compoundnbt.putString("id", rl.toString());
            if(EntityType.getKey(EntityType.LIGHTNING_BOLT).equals(rl))
            {
                LightningBoltEntity ent = new LightningBoltEntity(sw, l.getX(), l.getY(), l.getZ(), false);
                sw.addLightningBolt(ent);
                return ent;
            }

            Entity entity = EntityType.func_220335_a(compoundnbt, sw, (ent) ->
            {
                ent.setLocationAndAngles(l.getX(), l.getY(), l.getZ(), ent.rotationYaw, ent.rotationPitch);
                return sw.summonEntity(ent) ? ent : null;
            });
            if(entity != null && entity instanceof MobEntity)
            {
                ((MobEntity) entity).onInitialSpawn(sw, sw.getDifficultyForLocation(new BlockPos(entity)), SpawnReason.COMMAND, null, null);
            }
            return entity;
        });
        sm.registerFunction("entity.near", (sc, in) -> Utils.getLiving((Entity) in[0].get(sc), in[1].getDouble(sc)));
        sm.registerFunction("entity.setspeed", (sc, in) -> 
        {  
            ((LivingEntity) in[0].get(sc)).getAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).setBaseValue(in[1].getDouble(sc));
            return Void.TYPE;
        });
        sm.registerFunction("entity.setgrowingage", (sc, in) -> 
        {  
            ((AgeableEntity) in[0].get(sc)).setGrowingAge(in[1].getInt(sc));
            return Void.TYPE;
        });
        sm.registerFunction("entity.gettype", (sc, in) -> ((Entity) in[0].get(sc)).getType().getRegistryName().getPath());
        
        // ---------------------------------------------------------------------    
        // human commands
        // ---------------------------------------------------------------------  
        sm.registerFunction("human.spawn", (sc, in) -> 
        {  
            Location l = (Location) in[0].get(sc);
            World w = l.getWorld().getWorld();
            EntityHuman h = ModEntities.HUMAN.create(w);
            h.setPosition(l.getX(), l.getY(), l.getZ());       
            w.addEntity(h);
            return h;
        });
        sm.registerFunction("human.setskin", (sc, in) -> 
        {  
            ((EntityHuman) in[0].get(sc)).setSkinName(in[1].getString(sc));   
            return Void.TYPE; 
        });
        sm.registerFunction("human.setscale", (sc, in) -> 
        {  
            ((EntityHuman) in[0].get(sc)).setScale(in[1].getFloat(sc));   
            return Void.TYPE; 
        });
        sm.registerFunction("human.setslim", (sc, in) -> 
        {  
            ((EntityHuman) in[0].get(sc)).setSlim(in[1].getBoolean(sc));   
            return Void.TYPE; 
        });

        // ---------------------------------------------------------------------  
        // GMap-library   
        // ---------------------------------------------------------------------  
        sm.registerFunction("gmap.removeall", (sc, in) -> 
        {    
            final String map = in[0].getString(sc);
            scheduler.getWorker().add(() -> 
            {
                try
                {
                    scriptBank.removeMap(map);         
                }
                catch(Exception ex)
                {
                    scheduler.scheduleTask(() -> 
                    {
                        sc.getScriptManager().getLogger().print("Worker error", ex, null, sc.getName(), sc, -1);
                    });
                }
            });
            return Void.TYPE; 
        });
        sm.registerFunction("gmap.add", (sc, in) -> 
        {    
            final String map = in[0].getString(sc);
            final String key = in[1].getString(sc);
            final String value = in[2].getString(sc);
            scheduler.getWorker().add(() -> 
            {
                try
                {
                    scriptBank.addMapElement(map, key, value);        
                }
                catch(Exception ex)
                {
                    scheduler.scheduleTask(() -> 
                    {
                        sc.getScriptManager().getLogger().print("Worker error", ex, null, sc.getName(), sc, -1);
                    });
                }
            });
            return Void.TYPE; 
        });
        sm.registerFunction("gmap.remove", (sc, in) -> 
        {    
            final String map = in[0].getString(sc);
            final String key = in[1].getString(sc);
            scheduler.getWorker().add(() -> 
            {
                try
                {
                    scriptBank.removeMapElement(map, key);      
                }
                catch(Exception ex)
                {
                    scheduler.scheduleTask(() -> 
                    {
                        sc.getScriptManager().getLogger().print("Worker error", ex, null, sc.getName(), sc, -1);
                    });
                }
            });
            return Void.TYPE; 
        });
        sm.registerFunction("gmap.get", (sc, in) -> scriptBank.getMapValue(in[0].getString(sc), in[1].getString(sc)));
        sm.registerFunction("gmap.getordefault", (sc, in) -> 
        {
            Object o = scriptBank.getMapValue(in[0].getString(sc), in[1].getString(sc));
            if(o == null)
            {
                return in[2].get(sc);
            }
            return o;
        });

        // ---------------------------------------------------------------------  
        // GDMap-library   
        // ---------------------------------------------------------------------  
        sm.registerFunction("gdmap.removeall", (sc, in) -> 
        {    
            if(in.length >= 2)
            {
                final String map = in[0].getString(sc);
                final String key = in[1].getString(sc);
                scheduler.getWorker().add(() -> 
                {
                    try
                    {
                        scriptBank.removeDualMapElement(map, key);      
                    }
                    catch(Exception ex)
                    {
                        scheduler.scheduleTask(() -> 
                        {
                            sc.getScriptManager().getLogger().print("Worker error", ex, null, sc.getName(), sc, -1);
                        });
                    }
                });
            }
            else
            {
                final String map = in[0].getString(sc);
                scheduler.getWorker().add(() -> 
                {
                    try
                    {
                        scriptBank.removeDualMap(map); 
                    }
                    catch(Exception ex)
                    {
                        scheduler.scheduleTask(() -> 
                        {
                            sc.getScriptManager().getLogger().print("Worker error", ex, null, sc.getName(), sc, -1);
                        });
                    }
                });
            }
            return Void.TYPE; 
        });
        sm.registerFunction("gdmap.add", (sc, in) -> 
        {    
            final String map = in[0].getString(sc);
            final String key1 = in[1].getString(sc);
            final String key2 = in[2].getString(sc);
            final String value = in[3].getString(sc);
            scheduler.getWorker().add(() -> 
            {
                try
                {
                    scriptBank.addDualMapElement(map, key1, key2, value);  
                }
                catch(Exception ex)
                {
                    scheduler.scheduleTask(() -> 
                    {
                        sc.getScriptManager().getLogger().print("Worker error", ex, null, sc.getName(), sc, -1);
                    });
                }
            });
            return Void.TYPE; 
        });
        sm.registerFunction("gdmap.remove", (sc, in) -> 
        {    
            final String map = in[0].getString(sc);
            final String key1 = in[1].getString(sc);
            final String key2 = in[2].getString(sc);
            scheduler.getWorker().add(() -> 
            {
                try
                {
                    scriptBank.removeDualMapElement(map, key1, key2); 
                }
                catch(Exception ex)
                {
                    scheduler.scheduleTask(() -> 
                    {
                        sc.getScriptManager().getLogger().print("Worker error", ex, null, sc.getName(), sc, -1);
                    });
                }
            });
            return Void.TYPE; 
        });
        sm.registerFunction("gdmap.get", (sc, in) -> 
        {    
            return scriptBank.getDualMapValue(in[0].getString(sc), in[1].getString(sc), in[2].getString(sc)); 
        });
        sm.registerFunction("gdmap.getordefault", (sc, in) -> 
        {
            Object o = scriptBank.getDualMapValue(in[0].getString(sc), in[1].getString(sc), in[2].getString(sc));
            if(o == null)
            {
                return in[3].get(sc);
            }
            return o;
        });
        
        // ---------------------------------------------------------------------  
        // databank library   
        // ---------------------------------------------------------------------  

        sm.registerFunction("databank.prepare", (sc, in) -> 
        {    
            PreparedStatement p = dataBank.prepareUnsafeStatement(in[0].getString(sc));
            if(in.length <= 1 || in[1].getBoolean(sc))
            {
                sc.addCloseable(p);
            }
            return p; 
        });
        sm.registerFunction("databank.setint", (sc, in) -> 
        {    
            ((PreparedStatement) in[0].get(sc)).setInt(in[1].getInt(sc), in[2].getInt(sc));
            return Void.TYPE;
        });
        sm.registerFunction("databank.setlong", (sc, in) -> 
        {    
            ((PreparedStatement) in[0].get(sc)).setLong(in[1].getInt(sc), in[2].getLong(sc));
            return Void.TYPE;
        });
        sm.registerFunction("databank.setdouble", (sc, in) -> 
        {    
            ((PreparedStatement) in[0].get(sc)).setDouble(in[1].getInt(sc), in[2].getDouble(sc));
            return Void.TYPE;
        });
        sm.registerFunction("databank.setstring", (sc, in) -> 
        {    
            ((PreparedStatement) in[0].get(sc)).setString(in[1].getInt(sc), in[2].getString(sc));
            return Void.TYPE;
        });
        sm.registerFunction("databank.setbool", (sc, in) -> 
        {    
            ((PreparedStatement) in[0].get(sc)).setBoolean(in[1].getInt(sc), in[2].getBoolean(sc));
            return Void.TYPE;
        });
        sm.registerFunction("databank.getint", (sc, in) -> (double) ((ResultSet) in[0].get(sc)).getInt(in[1].getInt(sc)));
        sm.registerFunction("databank.getlong", (sc, in) -> (double) ((ResultSet) in[0].get(sc)).getLong(in[1].getInt(sc)));
        sm.registerFunction("databank.getdouble", (sc, in) -> ((ResultSet) in[0].get(sc)).getDouble(in[1].getInt(sc)));
        sm.registerFunction("databank.getstring", (sc, in) -> ((ResultSet) in[0].get(sc)).getString(in[1].getInt(sc)));
        sm.registerFunction("databank.getbool", (sc, in) -> ((ResultSet) in[0].get(sc)).getBoolean(in[1].getInt(sc)));
        sm.registerFunction("databank.execute", (sc, in) -> ((PreparedStatement) in[0].get(sc)).executeQuery());
        sm.registerFunction("databank.workerexecute", (sc, in) -> 
        {    
            final PreparedStatement p = (PreparedStatement) in[0].get(sc);
            scheduler.getWorker().add(() -> 
            {
                try
                {
                    p.execute();                
                }
                catch(SQLException ex)
                {
                    scheduler.scheduleTask(() -> 
                    {
                        sc.getScriptManager().getLogger().print("Worker error", ex, null, sc.getName(), sc, -1);
                    });
                }
                sc.removeCloseable(p);
                try
                {
                    p.close();
                }
                catch(SQLException ex)
                {
                    scheduler.scheduleTask(() -> 
                    {
                        sc.getScriptManager().getLogger().print("Worker error", ex, null, sc.getName(), sc, -1);
                    });
                }
            }); 
            return Void.TYPE;
        });
        sm.registerFunction("databank.next", (sc, in) -> ((ResultSet) in[0].get(sc)).next());
        sm.registerFunction("databank.close", (sc, in) -> 
        {    
            AutoCloseable auto = (AutoCloseable) in[0].get(sc);
            auto.close();
            sc.removeCloseable(auto);
            return Void.TYPE;
        });
        
        // ---------------------------------------------------------------------  
        // Plot-library   
        // ---------------------------------------------------------------------  
        sm.registerFunction("plot.get", (sc, in) -> 
        {  
            Location l = (Location) in[0].get(sc);
            return plots.getPlots(l.getWorld(), l.getBlockPos()); 
        });
        sm.registerFunction("plot.check", (sc, in) -> 
        {    
            Location l = (Location) in[0].get(sc);
            ModEntityPlayerMP p = (ModEntityPlayerMP) in[1].get(sc);
            int flags = in[2].getInt(sc);
            boolean empty = in[3].getBoolean(sc);
            return plots.canDoSomething(l.getWorld(), l.getBlockPos(), p, flags, empty);
        });
        sm.registerFunction("plot.setflags", (sc, in) -> 
        {    
            Plot p = (Plot) in[0].get(sc);
            p.setFlag(in[1].getInt(sc), in[2].getBoolean(sc));
            return Void.TYPE;
        });
        sm.registerFunction("plot.hasflags", (sc, in) -> ((Plot) in[0].get(sc)).hasFlags(in[1].getInt(sc)));
        sm.registerFunction("plot.getflags", (sc, in) -> (double) ((Plot) in[0].get(sc)).getFlags());
        sm.registerFunction("plot.getowners", (sc, in) -> ((Plot) in[0].get(sc)).getOwners());
        sm.registerFunction("plot.add", (sc, in) -> 
        {    
            Location l1 = (Location) in[0].get(sc);
            Location l2 = (Location) in[1].get(sc);
            if(l1.getWorld() != l2.getWorld())
            {
                throw new IllegalArgumentException("worlds not equal for locations");
            }
            return plots.add(l1.getWorld(), l1.getBlockPos(), l2.getBlockPos());
        });
        sm.registerFunction("plot.remove", (sc, in) -> 
        {    
            plots.remove((IWorld) in[1].get(sc), (Plot) in[0].get(sc));
            return Void.TYPE;
        });
        sm.registerFunction("plot.getname", (sc, in) -> ((Plot) in[0].get(sc)).getName());
        sm.registerFunction("plot.setname", (sc, in) -> 
        {    
            ((Plot) in[0].get(sc)).setName(in[1].getString(sc));
            return Void.TYPE;
        });
        sm.registerFunction("plot.getid", (sc, in) -> (double) ((Plot) in[0].get(sc)).getId());
        sm.registerFunction("plot.iterator", (sc, in) -> 
        {
            IWorld word = (IWorld) in[0].get(sc);
            if(in.length >= 2)
            {
                plots.getIterator(word, getUUID(in[1].get(sc)));
            }
            return plots.getIterator(word);
        });
        sm.registerFunction("plot.intersecting", (sc, in) -> 
                plots.getIntersectingPlots((IWorld) in[0].get(sc), in[1].getInt(sc), 
                        in[2].getInt(sc), in[3].getInt(sc), in[4].getInt(sc), 
                        in[5].getInt(sc), in[6].getInt(sc)));
        
        sm.registerFunction("plot.getminx", (sc, in) -> (double) ((Plot) in[0].get(sc)).getMinX());
        sm.registerFunction("plot.getminy", (sc, in) -> (double) ((Plot) in[0].get(sc)).getMinY());
        sm.registerFunction("plot.getminz", (sc, in) -> (double) ((Plot) in[0].get(sc)).getMinZ());
        sm.registerFunction("plot.getmaxx", (sc, in) -> (double) ((Plot) in[0].get(sc)).getMaxX());
        sm.registerFunction("plot.getmaxy", (sc, in) -> (double) ((Plot) in[0].get(sc)).getMaxY());
        sm.registerFunction("plot.getmaxz", (sc, in) -> (double) ((Plot) in[0].get(sc)).getMaxZ());
        
        // ---------------------------------------------------------------------  
        // block protection library   
        // ---------------------------------------------------------------------  
        sm.registerFunction("protect.hasaccess", (sc, in) -> 
        {  
            Location l = (Location) in[0].get(sc);
            BlockPos pos = l.getBlockPos();
            return blockProtection.hasAccess(pos, l.getWorld(), (ModEntityPlayerMP) in[1].get(sc));
        });
        
        // ---------------------------------------------------------------------  
        // Script-library   
        // ---------------------------------------------------------------------    
        sm.registerFunction("script.playerstolist", (sc, in) -> new ArrayList<>(scripts.getPlayerList(sc.getId()))); 
        sm.registerFunction("script.getplayeramount", (sc, in) -> (double) scripts.getPlayerList(sc.getId()).size()); 
        sm.registerFunction("script.start", (sc, in) -> 
        {  
            Object o = in[0].get(sc);
            if(o instanceof PlayerEntity)
            {
                String[] names = new String[in.length - 1];
                for(int i = 1; i < in.length; i++)
                {
                    names[i - 1] = in[i].getString(sc);
                }
                return scripts.startPlayerScript((PlayerEntity) in[0].get(sc), names);
            }
            String[] names = new String[in.length];
            names[0] = o.toString();
            for(int i = 1; i < in.length; i++)
            {
                names[i] = in[i].getString(sc);
            }
            scripts.startScript(names);
            return true;
        });
        sm.registerFunction("script.join", (sc, in) -> scripts.registerPlayer((Script) in[0].get(sc), (PlayerEntity) in[1].get(sc)));
        sm.registerFunction("script.kick", (sc, in) -> scripts.unregisterPlayer(sc, (PlayerEntity) in[0].get(sc)));
        sm.registerFunction("script.getleader", (sc, in) -> 
        { 
            List<UUID> players = scripts.getPlayerList(sc.getId());
            if(players.isEmpty())
            {
                return null;
            }
            return server.getPlayerList().getPlayerByUUID(players.get(0));
        });

        // ---------------------------------------------------------------------  
        // Scoreboard-library   
        // ---------------------------------------------------------------------  
        sm.registerFunction("sb.add", (sc, in) -> 
        {  
            int id = in[1].getInt(sc);
            String message = SnuviUtils.connect(sc, in, 2);
            doForGroup(server, scripts, perms, in[0].get(sc), sc, p -> ((ModEntityPlayerMP) p).getScoreboard().addText(id, message));
            return Void.TYPE; 
        });
        sm.registerFunction("sb.remove", (sc, in) -> 
        {  
            int id = in[1].getInt(sc);
            doForGroup(server, scripts, perms, in[0].get(sc), sc, p -> ((ModEntityPlayerMP) p).getScoreboard().removeText(id));
            return Void.TYPE; 
        });
        sm.registerFunction("sb.reset", (sc, in) -> 
        {  
            doForGroup(server, scripts, perms, in[0].get(sc), sc, p -> ((ModEntityPlayerMP) p).getScoreboard().clear((ModEntityPlayerMP) p)); 
            return Void.TYPE; 
        });
        
        // ---------------------------------------------------------------------  
        // Display-library   
        // ---------------------------------------------------------------------  
        sm.registerFunction("display.add", (sc, in) -> 
        {  
            byte id = in[1].getByte(sc);
            String message = SnuviUtils.connect(sc, in, 2);
            doForGroup(server, scripts, perms, in[0].get(sc), sc, p -> ModPacketHandler.sendToDisplay((ServerPlayerEntity) p, (byte) 1, id, message));
            return Void.TYPE; 
        }); 
        sm.registerFunction("display.remove", (sc, in) -> 
        {  
            byte id = in[1].getByte(sc);
            doForGroup(server, scripts, perms, in[0].get(sc), sc, p -> ModPacketHandler.sendToDisplay((ServerPlayerEntity) p, (byte) 2, id, ""));
            return Void.TYPE; 
        });
        sm.registerFunction("display.reset", (sc, in) -> 
        {  
            doForGroup(server, scripts, perms, in[0].get(sc), sc, p -> ModPacketHandler.sendToDisplay((ServerPlayerEntity) p, (byte) 3, (byte) -1, "")); 
            return Void.TYPE; 
        });
        
        // ---------------------------------------------------------------------  
        // status - library   
        // ---------------------------------------------------------------------  
        sm.registerFunction("status.add", (sc, in) -> 
        {  
            byte index = in[1].getByte(sc);
            String message = SnuviUtils.connect(sc, in, 2);
            doForGroup(server, scripts, perms, in[0].get(sc), sc, p -> ModPacketHandler.addStatus((ServerPlayerEntity) p, index, message));
            return Void.TYPE; 
        }); 
        sm.registerFunction("status.addtimed", (sc, in) -> 
        {  
            byte index = in[1].getByte(sc);
            int time = in[2].getInt(sc);
            String message = SnuviUtils.connect(sc, in, 3);
            doForGroup(server, scripts, perms, in[0].get(sc), sc, p -> ModPacketHandler.addTimedStatus((ServerPlayerEntity) p, index, message, time));
            return Void.TYPE; 
        }); 
        sm.registerFunction("status.remove", (sc, in) -> 
        {  
            byte index = in[1].getByte(sc);
            doForGroup(server, scripts, perms, in[0].get(sc), sc, p -> ModPacketHandler.removeStatus((ServerPlayerEntity) p, index));
            return Void.TYPE; 
        });
        sm.registerFunction("status.reset", (sc, in) -> 
        {  
            doForGroup(server, scripts, perms, in[0].get(sc), sc, p -> ModPacketHandler.clearStatus((ServerPlayerEntity) p)); 
            return Void.TYPE; 
        });
        
        // ---------------------------------------------------------------------  
        // ItemStack-Display-library   
        // ---------------------------------------------------------------------  
        sm.registerFunction("stacks.set", (sc, in) -> 
        {  
            byte index = in[1].getByte(sc);
            if(index < 0 || index >= 9)
            {
                throw new IllegalArgumentException("index must be beetween 0 and 8, given: " + index);
            }
            int iconIndex = in[2].getInt(sc);
            int count = in[3].getInt(sc);
            doForGroup(server, scripts, perms, in[0].get(sc), sc, p -> ModPacketHandler.setItemStackIndex((ServerPlayerEntity) p, index, iconIndex, count));
            return Void.TYPE; 
        }); 
        sm.registerFunction("stacks.clearindex", (sc, in) -> 
        {  
            byte index = in[1].getByte(sc);
            if(index < 0 || index >= 9)
            {
                throw new IllegalArgumentException("index must be beetween 0 and 8, given: " + index);
            }
            doForGroup(server, scripts, perms, in[0].get(sc), sc, p -> ModPacketHandler.setItemStackIndex((ServerPlayerEntity) p, index, -1, 0));
            return Void.TYPE; 
        }); 
        sm.registerFunction("stacks.clear", (sc, in) -> 
        {  
            doForGroup(server, scripts, perms, in[0].get(sc), sc, p -> ModPacketHandler.clearItemStacks((ServerPlayerEntity) p));
            return Void.TYPE; 
        });
        sm.registerFunction("stacks.setactive", (sc, in) -> 
        {  
            boolean active = in[1].getBoolean(sc);
            doForGroup(server, scripts, perms, in[0].get(sc), sc, p -> ModPacketHandler.setItemStackActive((ServerPlayerEntity) p, active));
            return Void.TYPE; 
        });
        
        // ---------------------------------------------------------------------  
        // Head-library   
        // ---------------------------------------------------------------------  
        sm.registerFunction("head.add", (sc, in) -> 
        {  
            byte id = in[1].getByte(sc);
            String name = in[2].getString(sc);
            int x = in[3].getInt(sc);
            int y = in[4].getInt(sc);
            byte scale = in[5].getByte(sc);
            doForGroup(server, scripts, perms, in[0].get(sc), sc, p -> ModPacketHandler.sendToHead((ServerPlayerEntity) p, (byte) 1, id, name, x, y, scale));
            return Void.TYPE; 
        }); 
        sm.registerFunction("head.remove", (sc, in) -> 
        {  
            byte id = in[1].getByte(sc);
            doForGroup(server, scripts, perms, in[0].get(sc), sc, p -> ModPacketHandler.sendToHead((ServerPlayerEntity) p, (byte) 2, id, "", -1, -1, (byte) -1));
            return Void.TYPE; 
        });
        sm.registerFunction("head.reset", (sc, in) -> 
        {  
            doForGroup(server, scripts, perms, in[0].get(sc), sc, p -> ModPacketHandler.sendToHead((ServerPlayerEntity) p, (byte) 3, (byte) -1, "", -1, -1, (byte) -1));
            return Void.TYPE; 
        });
        
        // ---------------------------------------------------------------------  
        // particle library   
        // --------------------------------------------------------------------- 
        sm.registerFunction("particle.get", (sc, in) -> 
        { 
            IParticleData data = Mapper.getParticle(in[0].getString(sc));
            if(data == ParticleTypes.BLOCK || data == ParticleTypes.FALLING_DUST)
            {
                data = new BlockParticleData((ParticleType<BlockParticleData>) data, Mapper.getBlock(in[1].getString(sc)).getDefaultState());
            }
            else if(data == ParticleTypes.DUST)
            {
                data = new RedstoneParticleData(in[1].getFloat(sc), in[2].getFloat(sc), in[3].getFloat(sc), in[4].getFloat(sc));
            }
            else if(data == ParticleTypes.ITEM)
            {
                data = new ItemParticleData((ParticleType<ItemParticleData>) data, new ItemStack(Mapper.getItem(in[1].getString(sc))));
            }
            return data; 
        });
        sm.registerFunction("particle.spawn", (sc, in) -> 
        { 
            Location l = ((Location) in[0].get(sc));
            IParticleData data = ((IParticleData) in[1].get(sc));
            int count = in.length >= 3 ? in[2].getInt(sc) : 1;
            double speed = in.length >= 4 ? in[3].getDouble(sc) : 0.0;
            double offX = in.length >= 5 ? in[4].getDouble(sc) : 0.0;
            double offY = in.length >= 6 ? in[5].getDouble(sc) : 0.0;
            double offZ = in.length >= 7 ? in[6].getDouble(sc) : 0.0;
            ((ServerWorld) l.getWorld()).spawnParticle(data, l.getX(), l.getY(), l.getZ(), count, offX, offY, offZ, speed);
            return Void.TYPE; 
        });
        sm.registerFunction("particle.spawncircle", (sc, in) -> 
        { 
            Location l = ((Location) in[0].get(sc));
            IParticleData data = ((IParticleData) in[1].get(sc));
            int instances = in[2].getInt(sc);
            double radius = in[3].getDouble(sc);
            int count = in.length >= 5 ? in[4].getInt(sc) : 1;
            double speed = in.length >= 6 ? in[5].getDouble(sc) : 0.0;
            double offX = in.length >= 7 ? in[6].getDouble(sc) : 0.0;
            double offY = in.length >= 8 ? in[7].getDouble(sc) : 0.0;
            double offZ = in.length >= 9 ? in[8].getDouble(sc) : 0.0;
            double x = l.getX();
            double y = l.getY();
            double z = l.getZ();
            ServerWorld sw = (ServerWorld) l.getWorld();           
            double angle = 2 * Math.PI / instances;
            for(int i = 0; i < instances; i++)
            {
                sw.spawnParticle(data, x + Math.cos(i * angle) * radius, y, z + Math.sin(i * angle) * radius, count, offX, offY, offZ, speed);
            }
            return Void.TYPE; 
        });
        sm.registerFunction("particle.spawnline", (sc, in) -> 
        { 
            Location l = ((Location) in[0].get(sc));
            IParticleData data = ((IParticleData) in[1].get(sc));
            int instances = in[2].getInt(sc);
            double stepX = in[3].getDouble(sc);
            double stepY = in[4].getDouble(sc);
            double stepZ = in[5].getDouble(sc);
            
            int count = in.length >= 7 ? in[6].getInt(sc) : 1;
            double speed = in.length >= 8 ? in[7].getDouble(sc) : 0.0;
            double offX = in.length >= 9 ? in[8].getDouble(sc) : 0.0;
            double offY = in.length >= 10 ? in[9].getDouble(sc) : 0.0;
            double offZ = in.length >= 11 ? in[10].getDouble(sc) : 0.0;
            double x = l.getX();
            double y = l.getY();
            double z = l.getZ();
            ServerWorld sw = (ServerWorld) l.getWorld();           
            for(int i = 0; i < instances; i++)
            {
                sw.spawnParticle(data, x + i * stepX, y + i * stepY, z + i * stepZ, count, offX, offY, offZ, speed);
            }
            return Void.TYPE; 
        });
        
        // ---------------------------------------------------------------------  
        // particle library   
        // --------------------------------------------------------------------- 
        sm.registerFunction("sound.get", (sc, in) -> Mapper.getSound(in[0].getString(sc)));
        sm.registerFunction("sound.getcategory", (sc, in) -> Mapper.getSoundCategory(in[0].getString(sc)));
        sm.registerFunction("sound.spawn", (sc, in) -> 
        { 
            Location l = (Location) in[0].get(sc);
            ServerWorld sw = (ServerWorld) l.getWorld();
            float volume = in.length >= 4 ? in[3].getFloat(sc) : 1.0f;
            float pitch = in.length >= 5 ? in[4].getFloat(sc) : (sw.rand.nextFloat() * 0.1f + 0.9f);
            sw.playSound(null, l.getX(), l.getY(), l.getZ(), (SoundEvent) in[1].get(sc), (SoundCategory) in[2].get(sc), volume, pitch);
            return Void.TYPE; 
        });
        sm.registerFunction("sound.spawnforplayer", (sc, in) -> 
        { 
            ServerPlayerEntity p = (ServerPlayerEntity) in[0].get(sc);
            float volume = in.length >= 4 ? in[3].getFloat(sc) : 1.0f;
            float pitch = in.length >= 5 ? in[4].getFloat(sc) : (p.world.rand.nextFloat() * 0.1f + 0.9f);
            p.connection.sendPacket(new SPlaySoundEffectPacket((SoundEvent) in[1].get(sc), 
                    (SoundCategory) in[2].get(sc), p.getPosX(), p.getPosY(), p.getPosZ(), volume, pitch));
            return Void.TYPE; 
        });

        // ---------------------------------------------------------------------  
        // Inventory-library   
        // ---------------------------------------------------------------------
        sm.registerFunction("inv.new", (sc, in) -> new ModInventory(in[0].getString(sc))); 
        sm.registerFunction("inv.getid", (sc, in) -> (double) ((ModInventory) in[0].get(sc)).getModId()); 
        sm.registerFunction("inv.loadblock", (sc, in) -> 
        { 
            Location l = (Location) in[0].get(sc);
            ChestTileEntity chest = (ChestTileEntity) l.getWorld().getTileEntity(l.getBlockPos());
            int size = chest.getSizeInventory();
            if(size % 9 != 0)
            {
                size /= 9;
                size++;
                size *= 9;
            }
            ModInventory inv = new ModInventory(size); 
            for(int i = 0; i < chest.getSizeInventory(); i++)
            {
                inv.setInventorySlotContents(i, chest.getStackInSlot(i).copy());
            }
            return inv; 
        });
        sm.registerFunction("inv.setitem", (sc, in) -> 
        { 
            ((IInventory) in[0].get(sc)).setInventorySlotContents(in[1].getInt(sc), (ItemStack) in[2].get(sc)); 
            return Void.TYPE; 
        });
        sm.registerFunction("inv.getitem", (sc, in) -> ((IInventory) in[0].get(sc)).getStackInSlot(in[1].getInt(sc)));
        sm.registerFunction("inv.open", (sc, in) -> 
        { 
            CustomContainer.openForPlayer((ServerPlayerEntity) in[1].get(sc), (ModInventory) in[0].get(sc), in[2].getString(sc), sc);
            return Void.TYPE; 
        });
        sm.registerFunction("inv.close", (sc, in) -> 
        { 
            ((PlayerEntity) in[0].get(sc)).closeScreen();
            return Void.TYPE; 
        });      
        sm.registerFunction("inv.update", (sc, in) ->  
        {
            ServerPlayerEntity p = (ServerPlayerEntity) in[0].get(sc);
            NonNullList<ItemStack> list = NonNullList.<ItemStack>create();
            int size = p.openContainer.inventorySlots.size();
            for(int j = 0; j < size; j++)
            {
                ItemStack itemstack = p.openContainer.inventorySlots.get(j).getStack();
                list.add(itemstack.isEmpty() ? ItemStack.EMPTY : itemstack);
            }
            p.sendAllContents(p.openContainer, list);
            return Void.TYPE; 
        }); 

        // ---------------------------------------------------------------------  
        // Read-library   
        // ---------------------------------------------------------------------
        sm.registerFunction("read.player", (sc, in) -> Utils.getPlayerByName(server, in[0].getString(sc)));
        sm.registerFunction("read.location", (sc, in) -> new Location(server, in[0].getString(sc)));
        sm.registerFunction("read.item", (sc, in) -> 
        {
            String s = in[0].getString(sc);
            if(s.startsWith("{"))
            {
                String left = SnuviUtils.connect(sc, in, 1);
                return ItemStackUtils.getStackFromNbtString(s + left);
            }
            Item item = Mapper.getItem(s);
            int amount = in.length >= 2 ? in[1].getInt(sc) : 1;
            ItemStack stack = new ItemStack(item, amount);
            if(in.length >= 3)
            {
                stack.setDisplayName(new StringTextComponent(in[2].getString(sc)));
            }
            if(in.length >= 4)
            {
                for(int i = 3; i < in.length; i++)
                {
                    ItemStackUtils.addLore(stack, in[i].getString(sc));
                }
            }
            return stack;
        });
        sm.registerFunction("read.spawnmob", (sc, in) -> 
        {
            Location l = (Location) in[0].get(sc);
            ServerWorld sw = (ServerWorld) l.getWorld();
            CompoundNBT compoundnbt = JsonToNBT.getTagFromJson(SnuviUtils.connect(sc, in, 1)); 
            Entity entity = EntityType.func_220335_a(compoundnbt, sw, (ent) ->
            {
                ent.setLocationAndAngles(l.getX(), l.getY(), l.getZ(), ent.rotationYaw, ent.rotationPitch);
                return sw.summonEntity(ent) ? ent : null;
            });
            return entity;
        });

        // ---------------------------------------------------------------------  
        // Text-library   
        // ---------------------------------------------------------------------
        sm.registerFunction("text.location", (sc, in) -> ((Location) in[0].get(sc)).toString());
        sm.registerFunction("text.locationblock", (sc, in) -> ((Location) in[0].get(sc)).toBlockString());
        sm.registerFunction("text.item", (sc, in) -> ItemStackUtils.getNbtString((ItemStack) in[0].get(sc)));
        sm.registerFunction("text.click", (sc, in) -> 
        {
            Object message = in[0].get(sc);
            ITextComponent text; 
            if(message instanceof ITextComponent)
            {
                text = (ITextComponent) message;
            }
            else
            {
                text = new StringTextComponent(String.valueOf(message));
            }
            Style style = text.getStyle();
            style.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, in[1].getString(sc)));
            return text;
        });
        sm.registerFunction("text.hover", (sc, in) -> 
        {
            Object message = in[0].get(sc);
            ITextComponent text; 
            if(message instanceof ITextComponent)
            {
                text = (ITextComponent) message;
            }
            else
            {
                text = new StringTextComponent(String.valueOf(message));
            }
            Style style = text.getStyle();
            style.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new StringTextComponent(in[1].getString(sc))));
            return text;
        });
        sm.registerFunction("text.link", (sc, in) -> 
        {
            StringTextComponent text = new StringTextComponent(in[0].getString(sc));
            Style style = text.getStyle();
            style.setColor(TextFormatting.RED);
            style.setBold(true);
            style.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, in[1].getString(sc)));
            return text;
        });
        sm.registerFunction("text.copytext", (sc, in) -> 
        {
            String s = in[1].getString(sc);
            //s = s.replace('{', 'Ɛ');
            //s = s.replace('}', 'Ƒ');
            //s = s.replace('[', 'ƒ');
            //s = s.replace(']', 'Ɠ');
            //s = s.replace('(', 'ƕ');
            //s = s.replace(')', 'Ɩ');
            s = s.replace(" ", "%20");
            //s = s.replace('\'', 'Ɩ');
            //s = s.replace('"', 'Ɩ');
            //s = s.replace("\\", "");

            StringTextComponent text = new StringTextComponent(in[0].getString(sc));
            Style style = text.getStyle();
            style.setColor(TextFormatting.RED);
            style.setBold(true);
            style.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "http://minecraft.hammerle.me/showtext.php/?text=" + s));
            return text;
        });
        sm.registerFunction("text.entity", (sc, in) -> 
        {
            CompoundNBT tag = new CompoundNBT();
            ((Entity) in[0].get(sc)).writeWithoutTypeId(tag);
            return tag.toString();
        });

        // ---------------------------------------------------------------------    
        // worker library
        // ---------------------------------------------------------------------  
        sm.registerFunction("worker.haswork", (sc, in) -> scheduler.getWorker().hasWork());
        
        // ---------------------------------------------------------------------    
        // ban library
        // ---------------------------------------------------------------------  
        sm.registerFunction("ban.kick", (sc, in) -> 
        {
            ((ServerPlayerEntity) in[0].get(sc)).connection.disconnect(new StringTextComponent(in[1].getString(sc)));
            return Void.TYPE;
        });
        sm.registerFunction("ban.add", (sc, in) -> 
        {
            GameProfile gp = server.getPlayerProfileCache().getProfileByUUID(getUUID(in[0].get(sc)));
            String reason = in[1].getString(sc);
            String banner = in[2].getString(sc);
            ProfileBanEntry entry;
            if(in.length >= 4)
            {
                GregorianCalendar calender = (GregorianCalendar) in[3].get(sc);
                Date d = new Date(calender.getTimeInMillis());
                entry = new ProfileBanEntry(gp, null, banner, d, reason);
            }
            else
            {
                entry = new ProfileBanEntry(gp, null, banner, null, reason);
            }
            server.getPlayerList().getBannedPlayers().addEntry(entry);
            return Void.TYPE;
        });
        sm.registerFunction("ban.remove", (sc, in) -> 
        {
            GameProfile gp = server.getPlayerProfileCache().getProfileByUUID(getUUID(in[0].get(sc)));
            server.getPlayerList().getBannedPlayers().removeEntry(gp);
            return Void.TYPE;
        });
        
        // ---------------------------------------------------------------------    
        // player data
        // ---------------------------------------------------------------------   
        sm.registerFunction("data.set", (sc, in) -> 
        {
            ((ModEntityPlayerMP) in[0].get(sc)).setVar(in[1].getString(sc), in[2].get(sc));
            return Void.TYPE;
        });
        sm.registerFunction("data.settimer", (sc, in) -> 
        {
            ((ModEntityPlayerMP) in[0].get(sc)).setTimer(in[1].getString(sc), in[2].getInt(sc), scheduler);
            return Void.TYPE;
        });
        sm.registerFunction("data.get", (sc, in) -> 
        {
            return ((ModEntityPlayerMP) in[0].get(sc)).getVar(in[1].getString(sc));
        });
        sm.registerFunction("data.gettimer", (sc, in) -> 
        {
            return (double) ((ModEntityPlayerMP) in[0].get(sc)).getTimer(in[1].getString(sc));
        });
        sm.registerFunction("data.clear", (sc, in) -> 
        {
            ((ModEntityPlayerMP) in[0].get(sc)).clearData(scheduler);
            return Void.TYPE;
        });
        
        // ---------------------------------------------------------------------    
        // no library
        // ---------------------------------------------------------------------   
        sm.registerFunction("setmotd", (sc, in) -> 
        {        
            server.getServerStatusResponse().setServerDescription(new StringTextComponent(in[0].getString(sc)));
            return Void.TYPE; 
        });
        sm.registerFunction("getglobalvar", (sc, in) -> 
        {         
            Object o = in[0].get(sc);
            if(in.length == 2)
            {
                return scriptBank.getVar(in[1].getString(sc), getId(playerBank, o));
            }
            return scriptBank.getVar(in[1].getString(sc), getId(playerBank, o), in[2].get(sc));
        });
        sm.registerAlias("getglobalvar", "ggv");
        sm.registerFunction("setglobalvar", (sc, in) -> 
        { 
            final String value = in[2].getString(sc);
            final String var = in[1].getString(sc);
            final int id = getId(playerBank, in[0].get(sc));
            scheduler.getWorker().add(() -> 
            {
                try
                {
                    scriptBank.setVar(value, var, id);
                }
                catch(Exception ex)
                {
                    scheduler.scheduleTask(() -> 
                    {
                        sc.getScriptManager().getLogger().print("Worker error", ex, null, sc.getName(), sc, -1);
                    });
                }
            });
            return Void.TYPE; 
        });                          
        sm.registerAlias("setglobalvar", "sgv");
        sm.registerFunction("delglobalvar", (sc, in) -> 
        { 
            final String var = in[1].getString(sc);
            final int id = getId(playerBank, in[0].get(sc));
            scheduler.getWorker().add(() -> 
            {
                try
                {
                    scriptBank.deleteVar(var, id);
                }
                catch(Exception ex)
                {
                    scheduler.scheduleTask(() -> 
                    {
                        sc.getScriptManager().getLogger().print("Worker error", ex, null, sc.getName(), sc, -1);
                    });
                }
            });
            return Void.TYPE; 
        }); 
        sm.registerAlias("delglobalvar", "dgv");
        sm.registerFunction("msg", (sc, in) -> 
        { 
            sendMessageToGroup(server, scripts, perms, in[0].get(sc), sc, concat(sc, 1, "", in)); 
            return Void.TYPE; 
        });       
        sm.registerFunction("removeformat", (sc, in) -> SnuviUtils.connect(sc, in, 0).replaceAll("§.", ""));           
        sm.registerFunction("command", (sc, in) -> 
        { 
            final String s = SnuviUtils.connect(sc, in, 0);
            scheduler.scheduleTask(() -> Server.executeCommand(s)); 
            return Void.TYPE;
        });
        sm.registerFunction("isplayer", (sc, in) -> 
        {
            Object o = in[0].get(sc);
            return o != null && o instanceof PlayerEntity;
        });  
    }
    
    private static Class getClass(String s) throws ClassNotFoundException
    {
        return Class.forName(s);
    }
    
    private static BlockState getBlockState(Location l)
    {
        return l.getWorld().getBlockState(l.getBlockPos());
    }

    private final static UUID SERVER_UUID = new UUID(0, 0);
    
    private static UUID getUUID(Object o)
    {
        if(o instanceof PlayerEntity)
        {
            return ((PlayerEntity) o).getUniqueID();
        }
        else if(o instanceof UUID)
        {
            return (UUID) o;
        }
        else if("SERVER".equals(o))
        {
            return SERVER_UUID;
        }
        return UUID.fromString(o.toString());
    }
    
    private static int getId(IPlayerBank bank, Object o)
    {
        if(o instanceof ModEntityPlayerMP)
        {
            return ((ModEntityPlayerMP) o).getId();
        }
        else if(o instanceof Double)
        {
            return ((Double) o).intValue();
        }
        UUID uuid = getUUID(o);
        return bank.getId(uuid);
    }
    
    public static void doForGroup(MinecraftServer server, Scripts scripts, PermissionManager perms, Object group, Script sc, Consumer<ICommandSource> c)
    {
        if(group instanceof String)
        {
            switch(group.toString().toLowerCase()) 
            {
                case "all":
                    PlayerList list = server.getPlayerList();
                    if(list != null)
                    {
                        scripts.getPlayerList(sc.getId()).forEach(uuid -> 
                        {
                            ServerPlayerEntity p = list.getPlayerByUUID(uuid);
                            if(p != null)
                            {
                                c.accept(p);
                            }
                        });
                    }
                    break;
                case "online":
                    if(server.getPlayerList() != null)
                    {
                        server.getPlayerList().getPlayers().forEach(p -> c.accept(p));
                    }   
                    break;
                case "dev":
                    if(server.getPlayerList() != null)
                    {
                        server.getPlayerList().getPlayers().forEach(p -> 
                        {
                            if(perms.hasPermission(p, "script.error"))
                            {
                                c.accept(p);
                            }
                        });
                    }
                    break;
                case "server":
                    c.accept(server);
                    break;
                default:
                    if(server.getPlayerList() != null)
                    {
                        c.accept(Utils.getPlayerByName(server, group.toString()));
                    }
                    break;
            }
            return;
        }
        c.accept((PlayerEntity) group);
    } 
    
    private static void sendMessageToGroup(MinecraftServer server, Scripts scripts, PermissionManager perms, Object group, Script sc, ITextComponent text)
    {
        doForGroup(server, scripts, perms, group, sc, p -> p.sendMessage(text));
    }
    
    private static ITextComponent concat(Script sc, int start, String pre, InputProvider... ob) throws Exception
    {
        StringTextComponent text = new StringTextComponent(pre);
        Object o;
        for(int i = start; i < ob.length; i++)
        {
            o = ob[i].get(sc);
            if(o instanceof ITextComponent)
            {
                text.appendSibling((ITextComponent) o);
            }
            else
            {
                text.appendText(String.valueOf(o));
            }
        }
        return text;
    }
    
    private static <T> T launchProjectile(PlayerEntity p, Class<? extends T> projectile, double scale, Object data)
    {
        World w = p.world;
        Entity launch = null;

        if(EntityItemProjectile.class == projectile)
        {
            if(data == null)
            {
                throw new NullPointerException("Data musn't be null for EntityItemProjectile");
            }
            ItemStack stack = (ItemStack) data;
            if(stack.isEmpty())
            {
                throw new IllegalArgumentException("Empty ItemStack not allowed here");
            }
            launch = new EntityItemProjectile(p, stack.copy());
            ((EntityItemProjectile) launch).setHeadingFromThrower(p, p.rotationPitch, p.rotationYaw, 0.0f, 1.5f, 1.0f);
        }
        else if(SnowballEntity.class == projectile)
        {
            launch = new SnowballEntity(w, p);
            ((SnowballEntity) launch).shoot(p, p.rotationPitch, p.rotationYaw, 0.0f, 1.5f, 1.0f);
        }
        else if(EggEntity.class == projectile)
        {
            launch = new EggEntity(w, p);
            ((EggEntity) launch).shoot(p, p.rotationPitch, p.rotationYaw, 0.0f, 1.5f, 1.0f);
        }
        else if(EnderPearlEntity.class == projectile)
        {
            launch = new EnderPearlEntity(w, p);
            ((EnderPearlEntity) launch).shoot(p, p.rotationPitch, p.rotationYaw, 0.0f, 1.5f, 1.0f);
        }
        else if(PotionEntity.class == projectile)
        {
            launch = new PotionEntity(w, p);
            ((PotionEntity) launch).setItem((ItemStack) data);
            ((PotionEntity) launch).shoot(p, p.rotationPitch, p.rotationYaw, -20.0f, 0.5f, 1.0f);
        }
        else if(ExperienceBottleEntity.class == projectile)
        {
            launch = new ExperienceBottleEntity(w, p);
            ((ExperienceBottleEntity) launch).shoot(p, p.rotationPitch, p.rotationYaw, -20.0f, 0.7f, 1.0f);
        }
        else if(AbstractArrowEntity.class.isAssignableFrom(projectile))
        {
            if(SpectralArrowEntity.class == projectile)
            {
                launch = new SpectralArrowEntity(w, p);
            }
            else
            {
                launch = new ArrowEntity(w, p);
                if(data != null)
                {
                    ((ArrowEntity) launch).setPotionEffect((ItemStack) data);
                }
            }
            ((AbstractArrowEntity) launch).shoot(p, p.rotationPitch, p.rotationYaw, 0.0F, 3.0F, 1.0F);
        }
        else if(DamagingProjectileEntity.class.isAssignableFrom(projectile))
        {
            Vec3d v = p.getLookVec().scale(10);
            if(SmallFireballEntity.class == projectile)
            {
                launch = new SmallFireballEntity(w, p, v.x, v.y, v.z);
            }
            else if(WitherSkullEntity.class == projectile)
            {
                launch = new WitherSkullEntity(w, p, v.x, v.y, v.z);
            }
            else if(DragonFireballEntity.class == projectile)
            {
                launch = new DragonFireballEntity(w, p, v.x, v.y, v.z);
            }
            else
            {
                launch = new FireballEntity(w, p, v.x, v.y, v.z);
            }
        }
        else
        {
            return null;
        }

        launch.setMotion(launch.getMotion().scale(scale));
        w.addEntity(launch);
        return (T) launch;
    }
}
