public static void registerAI(Class<? extends EntityAIBase> ai, String name, boolean isTarget, int ownerFieldPos)
{
    map.put(ai,name);
    CustomAIHelper.isTarget.put(ai,isTarget);
    ownerFields.put(ai, ownerFieldPos);
} public class ArrowProjectile extends Projectile
{

    public ArrowProjectile(String id)
    {
        super(id);
    }
    
    public void attackEntityWithRangedAttack(EntityLiving user, EntityLivingBase par1EntityLivingBase, float par2)
    {
        EntityArrow entityarrow = new EntityArrow(user.worldObj, user, par1EntityLivingBase, 1.0F, (float)(14 - user.worldObj.difficultySetting.getDifficultyId() * 4));
        int i = EnchantmentHelper.getEnchantmentLevel(Enchantment.power.effectId, user.getHeldItem());
        int j = EnchantmentHelper.getEnchantmentLevel(Enchantment.punch.effectId, user.getHeldItem());
        entityarrow.setDamage((double)(par2 * 2.0F) + user.getRNG().nextGaussian() * 0.25D + (double)((float)user.worldObj.difficultySetting.getDifficultyId() * 0.11F));

        if (i > 0)
        {
            entityarrow.setDamage(entityarrow.getDamage() + (double)i * 0.5D + 0.5D);
        }

        if (j > 0)
        {
            entityarrow.setKnockbackStrength(j);
        }

        if (EnchantmentHelper.getEnchantmentLevel(Enchantment.flame.effectId, user.getHeldItem()) > 0)
        {
            entityarrow.setFire(100);
        }

        user.playSound("random.bow", 1.0F, 1.0F / (user.getRNG().nextFloat() * 0.4F + 0.8F));
        user.worldObj.spawnEntityInWorld(entityarrow);
    }
} public class EntityAIFleeSunEvenNotBurning extends EntityAIBase
{
    private EntityCreature theCreature;
    private double shelterX;
    private double shelterY;
    private double shelterZ;
    private double movementSpeed;

    public EntityAIFleeSunEvenNotBurning(EntityCreature par1EntityCreature, double par2)
    {
        this.theCreature = par1EntityCreature;
        this.movementSpeed = par2;
        this.setMutexBits(1);
    }

    /**
     * Returns whether the EntityAIBase should begin execution.
     */
    public boolean shouldExecute()
    {
        if (!this.theCreature.worldObj.isDaytime())
        {
            return false;
        }
        else if (!this.theCreature.worldObj.canBlockSeeTheSky(MathHelper.floor_double(this.theCreature.posX), (int)this.theCreature.boundingBox.minY, MathHelper.floor_double(this.theCreature.posZ)))
        {
            return false;
        }
        else
        {
            Vec3 vec3 = this.findPossibleShelter();

            if (vec3 == null)
            {
                return false;
            }
            else
            {
                this.shelterX = vec3.xCoord;
                this.shelterY = vec3.yCoord;
                this.shelterZ = vec3.zCoord;
                return true;
            }
        }
    }

    /**
     * Returns whether an in-progress EntityAIBase should continue executing
     */
    public boolean continueExecuting()
    {
        return !this.theCreature.getNavigator().noPath();
    }

    /**
     * Execute a one shot task or start executing a continuous task
     */
    public void startExecuting()
    {
        this.theCreature.getNavigator().tryMoveToXYZ(this.shelterX, this.shelterY, this.shelterZ, this.movementSpeed);
    }

    private Vec3 findPossibleShelter()
    {
        Random random = this.theCreature.getRNG();

        for (int i = 0; i < 10; ++i)
        {
            int j = MathHelper.floor_double(this.theCreature.posX + (double)random.nextInt(20) - 10.0D);
            int k = MathHelper.floor_double(this.theCreature.boundingBox.minY + (double)random.nextInt(6) - 3.0D);
            int l = MathHelper.floor_double(this.theCreature.posZ + (double)random.nextInt(20) - 10.0D);

            if (!this.theCreature.worldObj.canBlockSeeTheSky(j, k, l) && this.theCreature.getBlockPathWeight(j, k, l) < 0.0F)
            {
                return this.theCreature.worldObj.getWorldVec3Pool().getVecFromPool((double)j, (double)k, (double)l);
            }
        }

        return null;
    }
} public class EntityAIFollowEntity extends EntityAIBase
{
    private Class<? extends Entity> target;
    private EntityLiving follower;
    private EntityLivingBase theTarget;
    private double followSpeed;
    private float max;

    public EntityAIFollowEntity(EntityLiving taskOwner, Class<? extends Entity> entityToFollow, double speed, float maxDist)
    {
        follower = taskOwner;
        this.target = entityToFollow;
        this.setMutexBits(3);
        followSpeed = speed;
        max = maxDist;
    }

    /**
     * Returns whether the EntityAIBase should begin execution.
     */
    public boolean shouldExecute()
    {
        List<?> list = this.follower.worldObj.getEntitiesWithinAABB(this.target, this.follower.boundingBox.expand(this.max, this.max, this.max));

        if (list.isEmpty())
        {
            return false;
        }
        else
        {
            Iterator<?> iterator = list.iterator();

            while (iterator.hasNext())
            {
                theTarget = (EntityLivingBase)iterator.next();
            }

            return this.theTarget != null;
        }
    }

    /**
     * Returns whether an in-progress EntityAIBase should continue executing
     */
    public boolean continueExecuting()
    {
        return this.follower.getDistanceSqToEntity(this.theTarget) < max && !theTarget.isDead;
    }

    /**
     * Execute a one shot task or start executing a continuous task
     */
    public void startExecuting()
    {
        if(theTarget instanceof EntityLiving)
        ((EntityLiving)theTarget).getNavigator().clearPathEntity();
    }

    /**
     * Resets the task
     */
    public void resetTask()
    {
        this.theTarget = null;
        this.follower.getNavigator().clearPathEntity();
    }

    /**
     * Updates the task
     */
    public void updateTask()
    {
        this.follower.getLookHelper().setLookPositionWithEntity(this.theTarget, 30.0F, 30.0F);
        this.follower.getNavigator().tryMoveToEntityLiving(this.theTarget, followSpeed);
    }
} public class EntityAIRangeAttack extends EntityAIBase
{
    /**
     * The entity the AI instance has been applied to
     */
    private final EntityLiving entityHost;
    private EntityLivingBase attackTarget;
    /**
     * A decrementing tick that spawns a ranged attack once this value reaches 0. It is then set back to the
     * maxRangedAttackTime.
     */
    private int rangedAttackTime;
    private double entityMoveSpeed;
    private int field_75318_f;
    private int reloadTime;
    /**
     * The maximum time the AI has to wait before performing another ranged attack.
     */
    private int maxRangedAttackTime;
    private float maxDist;
    private float maxDistSquared;
    private Projectile projectile;

    public EntityAIRangeAttack(EntityLiving living, double par2, int par4, int par5, float par6, Projectile projectileType)
    {
        this.rangedAttackTime = -1;

        if (!(living instanceof EntityLivingBase))
        {
            throw new IllegalArgumentException("RangeAttackGoal requires EntityLiving implements RangedAttackMob");
        }
        else
        {
            this.entityHost = living;
            this.entityMoveSpeed = par2;
            this.reloadTime = par4;
            this.maxRangedAttackTime = par5;
            this.maxDist = par6;
            this.maxDistSquared = par6 * par6;
            this.projectile = projectileType;
            this.setMutexBits(3);
        }
    }

    /**
     * Returns whether the EntityAIBase should begin execution.
     */
    public boolean shouldExecute()
    {
        EntityLivingBase entitylivingbase = this.entityHost.getAttackTarget();

        if (entitylivingbase == null)
        {
            return false;
        }
        else
        {
            this.attackTarget = entitylivingbase;
            return true;
        }
    }

    /**
     * Returns whether an in-progress EntityAIBase should continue executing
     */
    public boolean continueExecuting()
    {
        return this.shouldExecute() || !this.entityHost.getNavigator().noPath();
    }

    /**
     * Resets the task
     */
    public void resetTask()
    {
        this.attackTarget = null;
        this.field_75318_f = 0;
        this.rangedAttackTime = -1;
    }

    /**
     * Updates the task
     */
    public void updateTask()
    {
        double d0 = this.entityHost.getDistanceSq(this.attackTarget.posX, this.attackTarget.boundingBox.minY, this.attackTarget.posZ);
        boolean flag = this.entityHost.getEntitySenses().canSee(this.attackTarget);

        if (flag)
        {
            ++this.field_75318_f;
        }
        else
        {
            this.field_75318_f = 0;
        }

        if (d0 <= (double)this.maxDistSquared && this.field_75318_f >= 20)
        {
            this.entityHost.getNavigator().clearPathEntity();
        }
        else
        {
            this.entityHost.getNavigator().tryMoveToEntityLiving(this.attackTarget, this.entityMoveSpeed);
        }

        this.entityHost.getLookHelper().setLookPositionWithEntity(this.attackTarget, 30.0F, 30.0F);
        float f;

        if (--this.rangedAttackTime == 0)
        {
            if (d0 > (double)this.maxDistSquared || !flag)
            {
                return;
            }

            f = MathHelper.sqrt_double(d0) / this.maxDist;
            float f1 = f;

            if (f < 0.1F)
            {
                f1 = 0.1F;
            }

            if (f1 > 1.0F)
            {
                f1 = 1.0F;
            }

            projectile.attackEntityWithRangedAttack(this.entityHost,this.attackTarget, f1);
            this.rangedAttackTime = MathHelper.floor_float(f * (float)(this.maxRangedAttackTime - this.reloadTime) + (float)this.reloadTime);
        }
        else if (this.rangedAttackTime < 0)
        {
            f = MathHelper.sqrt_double(d0) / this.maxDist;
            this.rangedAttackTime = MathHelper.floor_float(f * (float)(this.maxRangedAttackTime - this.reloadTime) + (float)this.reloadTime);
        }
    }
} public class EntityAITeleportRandomly extends EntityAIBase
{

    private EntityLiving entity;
    private int tickRate;

    private int teleportDelay;
    private World worldObj;
    private boolean playSound;
    private boolean avoidLiquids;
    
    public EntityAITeleportRandomly(EntityLiving living, int tickRate, boolean playSound, boolean avoidLiquids)
    {
        this.entity = living;
        if(tickRate <= 0)
            tickRate = 1;
        this.tickRate = tickRate;
        worldObj = living.worldObj;
        this.playSound = playSound;
        this.avoidLiquids = avoidLiquids;
        this.setMutexBits(CustomAIHelper.MOVE_MUTEX_BITS);
    }
    
    public void startExecuting()
    {
        teleportRandomly();
    }
    
    /**
     * Teleport the entity to a random nearby position
     */
    protected boolean teleportRandomly()
    {
        double d0 = entity.posX + (entity.getRNG().nextDouble() - 0.5D) * 64.0D;
        double d1 = entity.posY + (double)(entity.getRNG().nextInt(64) - 32);
        double d2 = entity.posZ + (entity.getRNG().nextDouble() - 0.5D) * 64.0D;
        return this.teleportTo(d0, d1, d2);
    }

    /**
     * Teleport the entity to another entity
     */
    protected boolean teleportToEntity(Entity par1Entity)
    {
        Vec3 vec3 = this.worldObj.getWorldVec3Pool().getVecFromPool(entity.posX - par1Entity.posX, entity.boundingBox.minY + (double)(entity.height / 2.0F) - par1Entity.posY + (double)par1Entity.getEyeHeight(), entity.posZ - par1Entity.posZ);
        vec3 = vec3.normalize();
        double d0 = 16.0D;
        double d1 = entity.posX + (entity.getRNG().nextDouble() - 0.5D) * 8.0D - vec3.xCoord * d0;
        double d2 = entity.posY + (double)(entity.getRNG().nextInt(16) - 8) - vec3.yCoord * d0;
        double d3 = entity.posZ + (entity.getRNG().nextDouble() - 0.5D) * 8.0D - vec3.zCoord * d0;
        return this.teleportTo(d1, d2, d3);
    }

    /**
     * Teleport the entity to a specified location
     */
    protected boolean teleportTo(double par1, double par3, double par5)
    {
        EnderTeleportEvent event = new EnderTeleportEvent(entity, par1, par3, par5, 0);
        if (MinecraftForge.EVENT_BUS.post(event))
        {
            return false;
        }
        double d3 = entity.posX;
        double d4 = entity.posY;
        double d5 = entity.posZ;
        entity.posX = event.targetX;
        entity.posY = event.targetY;
        entity.posZ = event.targetZ;
        boolean flag = false;
        int i = MathHelper.floor_double(entity.posX);
        int j = MathHelper.floor_double(entity.posY);
        int k = MathHelper.floor_double(entity.posZ);

        if (this.worldObj.blockExists(i, j, k))
        {
            boolean flag1 = false;

            while (!flag1 && j > 0)
            {
                Block block = this.worldObj.getBlock(i, j - 1, k);

                if (block.getMaterial().blocksMovement())
                {
                    flag1 = true;
                }
                else
                {
                    --entity.posY;
                    --j;
                }
            }

            if (flag1)
            {
                entity.setPosition(entity.posX, entity.posY, entity.posZ);

                if (this.worldObj.getCollidingBoundingBoxes(entity, entity.boundingBox).isEmpty() && (!avoidLiquids || !this.worldObj.isAnyLiquid(entity.boundingBox)))
                {
                    flag = true;
                }
            }
        }

        if (!flag)
        {
            entity.setPosition(d3, d4, d5);
            return false;
        }
        else
        {
            short short1 = 128;

            for (int l = 0; l < short1; ++l)
            {
                double d6 = (double)l / ((double)short1 - 1.0D);
                float f = (entity.getRNG().nextFloat() - 0.5F) * 0.2F;
                float f1 = (entity.getRNG().nextFloat() - 0.5F) * 0.2F;
                float f2 = (entity.getRNG().nextFloat() - 0.5F) * 0.2F;
                double d7 = d3 + (entity.posX - d3) * d6 + (entity.getRNG().nextDouble() - 0.5D) * (double)entity.width * 2.0D;
                double d8 = d4 + (entity.posY - d4) * d6 + entity.getRNG().nextDouble() * (double)entity.height;
                double d9 = d5 + (entity.posZ - d5) * d6 + (entity.getRNG().nextDouble() - 0.5D) * (double)entity.width * 2.0D;
                this.worldObj.spawnParticle("portal", d7, d8, d9, (double)f, (double)f1, (double)f2);
            }

            if(playSound)
            {
                this.worldObj.playSoundEffect(d3, d4, d5, "mob.endermen.portal", 1.0F, 1.0F);
                entity.playSound("mob.endermen.portal", 1.0F, 1.0F);
            }
            return true;
        }
    }
    
    public boolean continueExecuting()
    {
        return false;
    }
    
    @Override
    public boolean shouldExecute()
    {
        boolean flag = entity.getRNG().nextInt(tickRate) <= 2;
        return flag;
    }

} public abstract class Projectile
{

    private String id;
    private final static HashMap<String, Projectile> projectileRegistry = new HashMap<String, Projectile>();

    protected Projectile(String id)
    {
        this.id = id;
    }
    
    public String getID()
    {
        return id;
    }
    
    public abstract void attackEntityWithRangedAttack(EntityLiving user, EntityLivingBase par1EntityLivingBase, float par2);

    public static void register(String id, Class<? extends Projectile> c)
    {
        try
        {
            Constructor<? extends Projectile> cons = c.getConstructor(new Class<?>[]{String.class});
            cons.setAccessible(true);
            projectileRegistry.put(id, cons.newInstance(new Object[]{id}));
        }
        catch (InstantiationException e)
        {
            e.printStackTrace();
        }
        catch (IllegalAccessException e)
        {
            e.printStackTrace();
        }
        catch (IllegalArgumentException e)
        {
            e.printStackTrace();
        }
        catch (InvocationTargetException e)
        {
            e.printStackTrace();
        }
        catch (NoSuchMethodException e)
        {
            e.printStackTrace();
        }
        catch (SecurityException e)
        {
            e.printStackTrace();
        }
    }

    public static Projectile fromString(String string)
    {
        return projectileRegistry.get(string);
    }

} public class WitherSkullProjectile extends Projectile
{

    private Random rand;

    public WitherSkullProjectile(String id)
    {
        super(id);
        rand = new Random();
    }

    private double func_82214_u(EntityLiving user, int par1)
    {
        if (par1 <= 0)
        {
            return user.posX;
        }
        else
        {
            float f = (user.renderYawOffset + (float)(180 * (par1 - 1))) / 180.0F * (float)Math.PI;
            float f1 = MathHelper.cos(f);
            return user.posX + (double)f1 * 1.3D;
        }
    }
   
    private double func_82208_v(EntityLiving user, int par1)
    {
        return par1 <= 0 ? user.posY + 3.0D : user.posY + 2.2D;
    }

    private double func_82213_w(EntityLiving user, int par1)
    {
        if (par1 <= 0)
        {
            return user.posZ;
        }
        else
        {
            float f = (user.renderYawOffset + (float)(180 * (par1 - 1))) / 180.0F * (float)Math.PI;
            float f1 = MathHelper.sin(f);
            return user.posZ + (double)f1 * 1.3D;
        }
    }
    
    private void func_82209_a(EntityLiving user, EntityLivingBase par1EntityLivingBase, int par1, double par2, double par4, double par6, boolean par8)
    {
        user.worldObj.playAuxSFXAtEntity((EntityPlayer)null, 1014, (int)user.posX, (int)user.posY, (int)user.posZ, 0);
        EntityWitherSkull2 entitywitherskull = new EntityWitherSkull2(user.worldObj, user, 0, 0, 0);

        if (par8)
        {
            entitywitherskull.setInvulnerable(true);
        }

        double d0 = par1EntityLivingBase.posX - user.posX;
        double d1 = par1EntityLivingBase.boundingBox.minY + (double)(user.height / 3.0F) - user.posY;
        double d2 = par1EntityLivingBase.posZ - user.posZ;
        double d3 = (double)MathHelper.sqrt_double(d0 * d0 + d2 * d2);

        if (d3 >= 1.0E-7D)
        {
            float f2 = (float)(Math.atan2(d2, d0) * 180.0D / Math.PI) - 90.0F;
            float f3 = (float)(-(Math.atan2(d1, d3) * 180.0D / Math.PI));
            double d4 = d0 / d3;
            double d5 = d2 / d3;
            user.setLocationAndAngles(user.posX + d4, user.posY+user.getEyeHeight()*2, user.posZ + d5, f2, f3);
            user.yOffset = 0.0F;
            float f4 = (float)d3 * 0.2F;
            this.setThrowableHeading(entitywitherskull, d0, d1 + (double)f4, d2, 1.5f, 1f);
        }
        user.worldObj.spawnEntityInWorld(entitywitherskull);
    }

    public void setThrowableHeading(EntityWitherSkull skull, double par1, double par3, double par5, float par7, float par8)
    {
        float f2 = MathHelper.sqrt_double(par1 * par1 + par3 * par3 + par5 * par5);
        par1 /= (double)f2;
        par3 /= (double)f2;
        par5 /= (double)f2;
        par1 += rand.nextGaussian() * (double)(rand.nextBoolean() ? -1 : 1) * 0.007499999832361937D * (double)par8;
        par3 += rand.nextGaussian() * (double)(rand.nextBoolean() ? -1 : 1) * 0.007499999832361937D * (double)par8;
        par5 += rand.nextGaussian() * (double)(rand.nextBoolean() ? -1 : 1) * 0.007499999832361937D * (double)par8;
        par1 *= (double)par7;
        par3 *= (double)par7;
        par5 *= (double)par7;
        skull.motionX = par1;
        skull.motionY = par3;
        skull.motionZ = par5;
        float f3 = MathHelper.sqrt_double(par1 * par1 + par5 * par5);
        skull.prevRotationYaw = skull.rotationYaw = (float)(Math.atan2(par1, par5) * 180.0D / Math.PI);
        skull.prevRotationPitch = skull.rotationPitch = (float)(Math.atan2(par3, (double)f3) * 180.0D / Math.PI);
    }

    /**
     * Attack the specified entity using a ranged attack.
     */
    public void attackEntityWithRangedAttack(EntityLiving user, EntityLivingBase par1EntityLivingBase, float par2)
    {
        this.func_82209_a(user, par1EntityLivingBase, 0, user.posX, user.posY + (double)user.getEyeHeight() * 0.5D, user.posZ, user.getRNG().nextFloat() < 0.001F);
    }

} public class BlockAIEmitter extends BlockContainer
{

    private IIcon topBlock;
    private IIcon bottom;
    private IIcon tinySide;

    public BlockAIEmitter()
    {
        super(Material.glass);
        this.setHardness(3.0F);
        this.setBlockName("ai_emitter");
        this.setCreativeTab(CreativeTabs.tabMisc);
    }

    @Override
    public TileEntity createNewTileEntity(World world, int metadata)
    {
        return new TileEntityAIEmitter(world);
    }

    /**
     * Called upon block activation (right click on the block.)
     */
    public boolean onBlockActivated(World world, int x, int y, int z, EntityPlayer player, int p_149727_6_, float p_149727_7_, float p_149727_8_, float p_149727_9_)
    {
        if(player.getCurrentEquippedItem() != null && player.getCurrentEquippedItem().getItem() == Items.dye)
        {
            TileEntityAIEmitter tileAIEmitter = (TileEntityAIEmitter)world.getTileEntity(x, y, z);
            
            if (tileAIEmitter != null)
            {
                tileAIEmitter.setBeamColor(ItemDye.field_150922_c[player.getCurrentEquippedItem().getItemDamage()]);
            }
            return true;
        }
        else
        {
            TileEntityAIEmitter tileAIEmitter = (TileEntityAIEmitter)world.getTileEntity(x, y, z);
            if(player.getCurrentEquippedItem() != null && player.getCurrentEquippedItem().getItem() == ModCustomAI.rewriterItem)
            {
                if (tileAIEmitter != null)
                {
                    player.openGui(ModCustomAI.instance, 1, world, x, y, z);
                }
            }
            else if(player.getCurrentEquippedItem() != null)
            {
                if (tileAIEmitter != null)
                {
                    tileAIEmitter.setStack(player.getCurrentEquippedItem());
                }
            }
            else if(player.getCurrentEquippedItem() == null)
            {
                if (tileAIEmitter != null)
                {
                    tileAIEmitter.setStack(null);;
                }
            }
            return true;
        }
    }

    /**
     * Is this block (a) opaque and (b) a full 1m cube?  This determines whether or not to render the shared face of two
     * adjacent blocks and also whether the player can attach torches, redstone wire, etc to this block.
     */
    public boolean isOpaqueCube()
    {
        return false;
    }

    /**
     * If this block doesn't render as an ordinary block it will return False (examples: signs, buttons, stairs, etc)
     */
    public boolean renderAsNormalBlock()
    {
        return false;
    }

    /**
     * The type of render function that is called for this block
     */
    @SideOnly(Side.CLIENT)
    public int getRenderType()
    {
        return ClientProxy.renderID;
    }
    
    public boolean shouldSideBeRendered(IBlockAccess access, int x, int y, int z, int side)
    {
        return true;
    }
    
    public IIcon getIcon(int side, int metadata)
    {
        return (IIcon) (side == -1 ? tinySide : side == 1 ? this.topBlock : (side == 0 ? bottom : (this.blockIcon)));
    }

    @SideOnly(Side.CLIENT)
    public void registerBlockIcons(IIconRegister register)
    {
        topBlock = Blocks.glass.getIcon(0, 0);
        bottom = Blocks.lapis_block.getIcon(0, 0);
        blockIcon = register.registerIcon(ModCustomAI.MODID+":ai_emitter_side");
        tinySide = register.registerIcon(ModCustomAI.MODID+":ai_emitter_side2");
    }

    /**
     * Called when the block is placed in the world.
     */
    public void onBlockPlacedBy(World world, int x, int y, int z, EntityLivingBase entity, ItemStack stack)
    {
        super.onBlockPlacedBy(world, x, y, z, entity, stack);

        if (stack.hasDisplayName())
        {
            ((TileEntityBeacon)world.getTileEntity(x, y, z)).func_145999_a(stack.getDisplayName());
        }
    }
} public class CommandTestForMod implements ICommand
{

    @Override
    public int compareTo(Object arg0)
    {
        return 0;
    }

    @Override
    public String getCommandName()
    {
        return "testforcustomai";
    }

    @Override
    public String getCommandUsage(ICommandSender var1)
    {
        return "/testforcustomai";
    }

    @Override
    public List getCommandAliases()
    {
        return null;
    }

    @Override
    public void processCommand(ICommandSender var1, String[] var2)
    {
        var1.addChatMessage(new ChatComponentText("The mod is here, don't worry!"));
    }

    @Override
    public boolean canCommandSenderUseCommand(ICommandSender var1)
    {
        return true;
    }

    @Override
    public List addTabCompletionOptions(ICommandSender var1, String[] var2)
    {
        return null;
    }

    @Override
    public boolean isUsernameIndex(String[] var1, int var2)
    {
        return false;
    }

} public class ConfigHandler
{

    private File configFile;
    private Properties propertiesMap;

    public ConfigHandler(File file)
    {
        this.configFile = file;
        if(file.exists())
        {
            load(file);
        }
        else
        {
            load(null);
        }
    }
    
    private void load(File file)
    {
        propertiesMap = new Properties();
        if(file != null)
        {
            try
            {
                FileInputStream in = new FileInputStream(file);
                propertiesMap.load(in);
                in.close();
            }
            catch(Exception e)
            {
                e.printStackTrace();
            }
        }
    }

    public synchronized boolean save()
    {
        try
        {
            FileOutputStream out = new FileOutputStream(configFile);
            propertiesMap.store(out, "CustomAI config file");
            out.flush();
            out.close();
        }
        catch(Exception e)
        {
            e.printStackTrace();
            return false;
        }
        
        return true;
    }
    
    public void set(String key, String value)
    {
        propertiesMap.setProperty(key, value);
        new Thread("Save CustomAI props thread")
        {
            public void run()
            {
                save();
            }
        }.start();
    }
    
    public String get(String key, String defaultValue)
    {
        if(propertiesMap.containsKey(key))
        {
            return propertiesMap.getProperty(key);
        }
        return defaultValue;
    }
    
    public float getFloat(String key, float defaultValue)
    {
        if(propertiesMap.containsKey(key))
        {
            return Float.parseFloat(propertiesMap.getProperty(key));
        }
        return defaultValue;
    }
    
    public double getDouble(String key, double defaultValue)
    {
        if(propertiesMap.containsKey(key))
        {
            return Double.parseDouble(propertiesMap.getProperty(key));
        }
        return defaultValue;
    }
    
    public boolean getBoolean(String key, boolean defaultValue)
    {
        if(propertiesMap.containsKey(key))
        {
            return Boolean.parseBoolean(propertiesMap.getProperty(key));
        }
        return defaultValue;
    }
    
    public int getInt(String key, int defaultValue)
    {
        if(propertiesMap.containsKey(key))
        {
            return Integer.parseInt(propertiesMap.getProperty(key));
        }
        return defaultValue;
    }

} public class CustomAIGuiHandler implements IGuiHandler
{

    @Override
    public Object getServerGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z)
    {
        if(ID == 0)
        {
            CustomAIPlayerExtendedProperties props = (CustomAIPlayerExtendedProperties) player.getExtendedProperties(ModCustomAI.MODID);
            if(props == null)
            {
                System.err.println("Server: props are null!");
                return null;
            }
            else
            {
                if(CustomAIHelper.hasEntityAI(world.getEntityByID(props.getEntityClickedID())))
                {
                }
                else
                    player.addChatMessage(new ChatComponentText("§4This mob doesn't support AI tasks. Sorry :("));
            }
        }
        else if(ID == 1)
        {
            
        }
        return null;
    }

    @Override
    public Object getClientGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z)
    {
        if(ID == 0)
        {
            CustomAIPlayerExtendedProperties props = (CustomAIPlayerExtendedProperties) player.getExtendedProperties(ModCustomAI.MODID);
            if(props == null)
            {
                System.err.println("Client: props are null!");
                return null;
            }
            else
            {
                if(CustomAIHelper.hasEntityAI(world.getEntityByID(props.getEntityClickedID())))
                {
                    return new GuiCustomAI(player, world, world.getEntityByID(props.getEntityClickedID()));
                }
            }
        }
        else if(ID == 1)
        {
            return new GuiAIEmitter(player, world, (TileEntityAIEmitter)world.getTileEntity(x, y, z),x, y, z);
        }
        return null;
    }

} public final class CustomAIHelper
{

    public static HashMap<Class<? extends EntityAIBase>, String> map = new HashMap<Class<? extends EntityAIBase>, String>();
    public static HashMap<Class<? extends EntityAIBase>, Boolean> isTarget = new HashMap<Class<? extends EntityAIBase>, Boolean>();
    public static HashMap<Class<? extends EntityAIBase>, Integer> ownerFields = new HashMap<Class<? extends EntityAIBase>, Integer>();
    public static HashMap<Class<? extends EntityAIBase>, Class<? extends EntityAIBase>> ownerFieldClasses = new HashMap<Class<? extends EntityAIBase>, Class<? extends EntityAIBase>>();
    
    public static final int TARGET_MUTEX_BITS = 1;
    public static final int MOVE_MUTEX_BITS = 1;
    public static final int WATCH_MUTEX_BITS = 2;
    public static final int ENTITY_INTERACT_MUTEX_BITS = 3;
    public static final int SWIMMING_MUTEX_BITS = 4;
    public static final int SIT_MUTEX_BITS = 5;
    public static final int SPECIAL_MUTEX_BITS = 7;
    
    public static void registerAI(Class<? extends EntityAIBase> ai, String name, boolean isTarget, int ownerFieldPos)
    {
        map.put(ai,name);
        CustomAIHelper.isTarget.put(ai,isTarget);
        ownerFields.put(ai, ownerFieldPos);
    }
    
    public static void registerAI(Class<? extends EntityAIBase> ai, String name, boolean isTarget, int ownerFieldPos, Class<? extends EntityAIBase> ownerFieldClass)
    {
        registerAI(ai, name, isTarget, ownerFieldPos);
        ownerFieldClasses.put(ai, ownerFieldClass);
    }
    
    public static boolean hasEntityAI(Entity entity)
    {
        if(entity != null)
        {
            if(entity instanceof EntityLiving)
            {
                EntityLiving living = (EntityLiving)entity;
                Method method = findMethod(EntityLiving.class.getDeclaredMethods(),"isAIEnabled","func_70650_aV");
                method.setAccessible(true);
                try
                {
                    Boolean result = (Boolean) method.invoke(living, new Object[]{});
                    if(result)
                    {
                       return true; 
                    }
                    else
                    {
                        return false;
                    }
                }
                catch (IllegalAccessException e)
                {
                    e.printStackTrace();
                }
                catch (IllegalArgumentException e)
                {
                    e.printStackTrace();
                }
                catch (InvocationTargetException e)
                {
                    e.printStackTrace();
                }
            }
        }
        return false;
    }

    private static Method findMethod(Method[] declaredMethods, String nonObfuscatedName, String obfuscatedName)
    {
        for(Method m : declaredMethods)
        {
            if(m.getName().equals(nonObfuscatedName) || m.getName().equals(obfuscatedName))
                return m;
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    public static List<EntityAITaskEntry> getTasksList(Entity entity)
    {
        if(hasEntityAI(entity))
        {
            EntityLiving living = (EntityLiving)entity;
            return living.tasks.taskEntries;
        }
        return null;
    }
    
    @SuppressWarnings("unchecked")
    public static List<EntityAITaskEntry> getTargetTasksList(Entity entity)
    {
        if(hasEntityAI(entity))
        {
            EntityLiving living = (EntityLiving)entity;
            return living.targetTasks.taskEntries;
        }
        return null;
    }

    public static String getNameForTask(Class<? extends EntityAIBase> action)
    {
        if(map.containsKey(action))
        {
            return map.get(action);
        }
        String s = action.getCanonicalName();
        if(s.contains("."))
        {
            return s.substring(s.lastIndexOf('.')+1);
        }
        return s;
    }
    
    public static String getNameForTask(EntityAIBase action)
    {
        if(map.containsKey(action.getClass()))
        {
            return map.get(action.getClass());
        }
        String s = action.getClass().getCanonicalName();
        if(s.contains("."))
        {
            return s.substring(s.lastIndexOf('.')+1);
        }
        return s;
    }

    public static JSONObject generateJSONFromAI(EntityLiving entity, EntityAITaskEntry entry)
    {
        return EntityAIFactory.instance().generateJSON(entity, entry);
    }
    
    public static void applyLists(Entity entity, List<EntityAITaskEntry> tasks, List<EntityAITaskEntry> targetTasks)
    {
        if(hasEntityAI(entity))
        {
            clearExecutingTasks(entity);
            clearExecutingTargetTasks(entity);
            getTasksList(entity).clear();
            getTargetTasksList(entity).clear();
            getTargetTasksList(entity).addAll(targetTasks);
            getTasksList(entity).addAll(tasks);
            
            for(EntityAITaskEntry entry : tasks)
            {
                if(entry != null && entry.action != null)
                {
                    setOwner(entry.action, entity);
                    if(entry.action instanceof EntityAIDoorInteract)
                    {
                        ((EntityLiving)entity).getNavigator().setEnterDoors(true);
                        ((EntityLiving)entity).getNavigator().setBreakDoors(true);
                    }
                }
            }
        }
    }
    
    private static void setOwner(EntityAIBase action, Entity entity)
    {
        Class<? extends EntityAIBase> clazz = (Class<? extends EntityAIBase>) action.getClass();
        Integer i = ownerFields.get(clazz);
        Class<? extends EntityAIBase> c = ownerFieldClasses.get(clazz);
        if(i != null && i >= 0)
        {
            try
            {
                Field f = (c == null ? clazz : c).getDeclaredFields()[i];
                f.setAccessible(true);
                f.set(action, entity);
            }
            catch (IllegalArgumentException e)
            {
                e.printStackTrace();
            }
            catch (IllegalAccessException e)
            {
                e.printStackTrace();
            }
        }
    }

    private static void clearExecutingTargetTasks(Entity entity)
    {
        if(hasEntityAI(entity))
        {
            EntityLiving living = (EntityLiving)entity;
            EntityAITasks targetTasks = living.tasks;
            ((List<?>)ObfuscationReflectionHelper.getPrivateValue(EntityAITasks.class, targetTasks, 2)).clear();
        }
    }

    private static void clearExecutingTasks(Entity entity)
    {
        if(hasEntityAI(entity))
        {
            EntityLiving living = (EntityLiving)entity;
            EntityAITasks tasks = living.tasks;
            ((List<?>)ObfuscationReflectionHelper.getPrivateValue(EntityAITasks.class, tasks, 2)).clear();
        }
    }

    public static EntityAITaskEntry generateAIFromJSON(Entity entity, String jsonData)
    {
        if(!hasEntityAI(entity) && entity  != null)
            return null;
        return EntityAIFactory.instance().generateAIBase((EntityLiving)entity,jsonData);
    }

    public static boolean isSuitableForEntity(EntityLiving entity, Class<? extends EntityAIBase> c)
    {
        EntityAIWorker worker = EntityAIFactory.instance().getWorkerFromClass(c);
        if(worker == null)
            return false;
        return worker.isSuitableForEntity(entity, c);
    }

    public static String getNameFromClass(Class<? extends EntityAIBase> c)
    {
        return map.get(c);
    }
    
    public static Class<? extends EntityAIBase> getClassFromName(String name)
    {
        Iterator<Class<? extends EntityAIBase>> it = map.keySet().iterator();
        while(it.hasNext())
        {
            Class<? extends EntityAIBase> c = it.next();
            if(map.get(c).equals(name))
            {
                return c;
            }
        }
        return null;
    }

    public static EntityAITaskEntry createDummyAITask(EntityLiving e, String value)
    {
        JSONObject json = new JsonDummyObject(getClassFromName(value),1);
        return generateAIFromJSON(e, json);
    }
    
    public static JSONObject createDummyJSON(Class<? extends EntityAIBase> ai)
    {
        return EntityAIFactory.instance().createDefaultJSON(ai);
    }

    public static EntityAITaskEntry generateAIFromJSON(EntityLiving entity, JSONObject json)
    {
        return EntityAIFactory.instance().generateAIBase(entity, json);
    }

    /**
     * Only for testing/map making purpose, never used actually by the mod
     * @return
     */
    public static String getCVSList()
    {
        String s = "Name;Class;Field1=DefaultValue1|Field2=DefaultValue2\n";
        Set<Class<? extends EntityAIBase>> keyset = map.keySet();
        Iterator<Class<? extends EntityAIBase>> it = keyset.iterator();
        final HashMap<String, String> tmpMap = new HashMap<String, String>();
        ArrayList<String> tmpList = new ArrayList<String>();
        while(it.hasNext())
        {
            Class<? extends EntityAIBase> c = it.next();
            String name = map.get(c);
            String list = createList(EntityAIFactory.instance().createDefaultJSON(c));
            String s1 = name+";"+c.getCanonicalName()+";"+list;
            tmpMap.put(s1, name);
            tmpList.add(s1);
        }
        Collections.sort(tmpList, new Comparator<String>()
                {
                    @Override
                    public int compare(String o1, String o2)
                    {
                        String s1 = tmpMap.get(o1);
                        String s2 = tmpMap.get(o2);
                        return s1.compareTo(s2);
                    }
                });
        for(String t : tmpList)
        {
            s+=t+"\n";
        }
        return s;
    }

    @SuppressWarnings("unchecked")
    private static String createList(JSONObject json)
    {
        String list = "";
        Iterator<String> it = json.keys();
        int index = 0;
        while(it.hasNext())
        {
            String key = it.next();
            if(index++ != 0)
            {
                list+=" | ";
            }
            list+=key+"="+json.get(key);
        }
        return list;
    }

    public static Class<? extends EntityAIBase> tryToGetAITaskFromName(String clazz)
    {
        Set<Class<? extends EntityAIBase>> tasks = map.keySet();
        Iterator<Class<? extends EntityAIBase>> it = tasks.iterator();
        while(it.hasNext())
        {
            Class<? extends EntityAIBase> c = it.next();
            String s = map.get(c);
            if(s.equalsIgnoreCase(clazz))
            {
                return c;
            }
        }
        return null;
    }
} public class EntityEvents
{

    @SubscribeEvent
    public void onEntityConstructing(EntityConstructing e)
    {
        if(e.entity instanceof EntityPlayer)
        {
            IExtendedEntityProperties props = e.entity.getExtendedProperties(ModCustomAI.MODID+"_Player");
            if(props == null)
            {
                e.entity.registerExtendedProperties(ModCustomAI.MODID, new CustomAIPlayerExtendedProperties());
            }
        }
        if(CustomAIHelper.hasEntityAI(e.entity))
        {
            IExtendedEntityProperties props = e.entity.getExtendedProperties(ModCustomAI.MODID+"_Entity");
            if(props == null)
            {
                e.entity.registerExtendedProperties(ModCustomAI.MODID, new CustomAIEntityExtendedProperties());
            }
        }
    }
    
    @SubscribeEvent
    public void onEntityInteraction(EntityInteractEvent e)
    {
        ItemStack stack = e.entityPlayer.getCurrentEquippedItem();
        if(stack != null)
        {
            if(stack.getItem() == ModCustomAI.rewriterItem)
            {
                e.setCanceled(true);
                CustomAIPlayerExtendedProperties props = (CustomAIPlayerExtendedProperties)e.entityPlayer.getExtendedProperties(ModCustomAI.MODID);
                if(props == null)
                {
                    e.entityPlayer.registerExtendedProperties(ModCustomAI.MODID, new CustomAIPlayerExtendedProperties());
                }
                props.setEntityInteractingWith(e.target.getEntityId(), e.target.getClass());
                FMLNetworkHandler.openGui(e.entityPlayer, ModCustomAI.instance, 0, e.entityPlayer.worldObj, (int)Math.floor(e.entityPlayer.posX), (int)Math.floor(e.entityPlayer.posY), (int)Math.floor(e.entityPlayer.posZ));
            }
            else if(stack.getItem() == ModCustomAI.copierItem)
            {
                e.setCanceled(!((AICopierItem)stack.getItem()).onInteract(e.entityPlayer.worldObj, stack, e.entityPlayer, e.target));
            }
        }
    }
    
    @SubscribeEvent
    public void onEntitySpawn(EntityJoinWorldEvent e)
    {
        if(e.world.isRemote)
        {
            ModCustomAI.packetPipeline.sendToServer(new PacketGetAI(e.entity.getEntityId()));
        }
    }
} public class Proxy
{

    public void registerStuff()
    {
        
    }
} public class TileEntityAIEmitter extends TileEntity implements IInventory
{

    private ItemStack stack;
    private Random rand;
    private List<JSONObject> tasks;
    private List<JSONObject> targetTasks;
    private int beamColor;
    private int range;
    public static HashMap<Item, Class<? extends Entity>> itemMap = new HashMap<Item, Class<? extends Entity>>();
    public static HashMap<Block, Class<? extends Entity>> blockMap = new HashMap<Block, Class<? extends Entity>>();

    public TileEntityAIEmitter()
    {
        this.range = 40;
        beamColor = 0xFFFFFFFF;
        tasks = new ArrayList<JSONObject>();
        targetTasks = new ArrayList<JSONObject>();
//        tasks.add(new JSONObject("{type:net.minecraft.entity.ai.EntityAIBreakDoor,priority:5}"));
    }
    
    public TileEntityAIEmitter(World world)
    {
        this();
        this.worldObj = world;
        this.rand = new Random();
    }

    @Override
    public int getSizeInventory()
    {
        return 1;
    }

    @Override
    public ItemStack getStackInSlot(int var1)
    {
        return stack;
    }

    @Override
    public ItemStack decrStackSize(int var1, int var2)
    {
        stack.stackSize--;
        return stack;
    }

    @Override
    public ItemStack getStackInSlotOnClosing(int var1)
    {
        return stack;
    }
    
    public void setStack(ItemStack s)
    {
        if(this.worldObj.isRemote)
        {
            ModCustomAI.packetPipeline.sendToServer(new PacketAIEStackUpdate(xCoord,yCoord,zCoord,s));
        }
        else
        {
            ModCustomAI.packetPipeline.sendToAll(new PacketAIEStackUpdate(xCoord,yCoord,zCoord,s));
        }
    }

    @Override
    public void setInventorySlotContents(int var1, ItemStack var2)
    {
        stack = var2;
    }

    @Override
    public String getInventoryName()
    {
        return "AI Emitter";
    }

    @Override
    public boolean hasCustomInventoryName()
    {
        return true;
    }

    @Override
    public int getInventoryStackLimit()
    {
        return 1;
    }

    @Override
    public boolean isUseableByPlayer(EntityPlayer var1)
    {
        return true;
    }
    
    @SuppressWarnings("unchecked")
    public void updateEntity()
    {
        super.updateEntity();
        List<Entity> entities = worldObj.getEntitiesWithinAABB(Entity.class, AxisAlignedBB.getBoundingBox(xCoord, yCoord, zCoord, xCoord+1, yCoord+1, zCoord+1).expand(range, 256, range));
        for(Entity e : entities)
        {
            if(CustomAIHelper.hasEntityAI(e) && isValid(e))
            {
                tryAddAITasks(e, tasks, targetTasks);
            }
        }
    }

    public boolean isValid(Entity e)
    {
        if(stack == null)
            return true;
        Item item = stack.getItem();
        Class<? extends Entity> c = itemMap.get(item);
        if(c == null)
        {
            if(item instanceof ItemBlock)
            {
                ItemBlock itemBlock = (ItemBlock)item;
                c = blockMap.get(itemBlock.field_150939_a);
                if(c == null)
                    return true;
                else
                    return Reflect.isInstanceof(e.getClass(), c);
            }
            return true;
        }
        else 
        {
            return Reflect.isInstanceof(e.getClass(), c);
        }
    }

    private void tryAddAITasks(Entity e, List<JSONObject> tasksAsJSON, List<JSONObject> targetTasksAsJSON)
    {
        List<EntityAITaskEntry> tasks = new ArrayList<EntityAITaskEntry>();
        List<EntityAITaskEntry> targetTasks = new ArrayList<EntityAITaskEntry>();
        for(JSONObject t : tasksAsJSON)
        {
            EntityAITaskEntry entry = CustomAIHelper.generateAIFromJSON((EntityLiving)e, t);
            if(entry != null && entry.action != null)
                tasks.add(entry);
        }
        for(JSONObject t : targetTasksAsJSON)
        {
            EntityAITaskEntry entry = CustomAIHelper.generateAIFromJSON((EntityLiving)e, t);
            if(entry != null && entry.action != null)
                targetTasks.add(entry);
        }
        List<EntityAITaskEntry> entTasks = (List<EntityAITaskEntry>)clone(CustomAIHelper.getTasksList(e));
        List<EntityAITaskEntry> entTargetTasks = (List<EntityAITaskEntry>)clone(CustomAIHelper.getTargetTasksList(e));
        List<EntityAITaskEntry> tasksToRemove = new ArrayList<EntityAITaskEntry>();
        List<EntityAITaskEntry> targetTasksToRemove = new ArrayList<EntityAITaskEntry>();
        for(EntityAITaskEntry entTask : entTasks)
        {
            for(EntityAITaskEntry task : tasks)
            {
                if(areTasksEqual(e,task, entTask))
                    tasksToRemove.add(entTask);
            }
        }
        for(EntityAITaskEntry entTargetTask : entTargetTasks)
        {
            for(EntityAITaskEntry targetTask : targetTasks)
            {
                if(areTasksEqual(e,targetTask, entTargetTask))
                    targetTasksToRemove.add(entTargetTask);
            }
        }
        entTasks.removeAll(tasksToRemove);
        entTargetTasks.removeAll(targetTasksToRemove);
        for(EntityAITaskEntry task : tasks)
        {
            if(task != null && task.action != null)
            {
                if(CustomAIHelper.isSuitableForEntity((EntityLiving) e, task.action.getClass()))
                {
                    entTasks.add(task);
                }
            }
        }
        for(EntityAITaskEntry targetTask : targetTasks)
        {
            if(targetTask != null && targetTask.action != null)
                if(CustomAIHelper.isSuitableForEntity((EntityLiving) e, targetTask.action.getClass()))
                {
                    entTargetTasks.add(targetTask);
                }
        }
        CustomAIHelper.applyLists(e, entTasks, entTargetTasks);
    }

    private List<EntityAITaskEntry> clone(List<EntityAITaskEntry> list)
    {
        ArrayList<EntityAITaskEntry> result = new ArrayList<EntityAITaskEntry>();
        result.addAll(list);
        return result;
    }

    /**
     * Check if two tasks are basically equal. Doesn't care about priority of each task
     * @param e : Entity used to process the instances of JSONObject 
     * @param task : First task of the couple to check
     * @param task2 : Second task of the couple to check
     * @return true if tasks are equal
     */
    @SuppressWarnings("unchecked")
    private boolean areTasksEqual(Entity e, EntityAITaskEntry task, EntityAITaskEntry task2)
    {
        if(task == null || task2 == null || task.action == null || task2.action == null)
            return false;
        JSONObject firstTask = CustomAIHelper.generateJSONFromAI((EntityLiving) e, task);
        JSONObject secondTask = CustomAIHelper.generateJSONFromAI((EntityLiving) e, task2);
        if(firstTask.keySet().size() != secondTask.keySet().size())
            return false;
        else
        {
            if(!firstTask.getString("type").equals(secondTask.getString("type")))
                return false;
            Iterator<String> keys = firstTask.keys();
            while(keys.hasNext())
            {
                String key = keys.next(); 
                if(!key.equals("priority"))
                {
                    try
                    {
                        if(!firstTask.get(key).equals(secondTask.get(key)))
                        {
                            return false;
                        }
                    }
                    catch(Exception exception)
                    {
                        return false;
                    }
                }
            }
            return true;
        }
    }
    
    public int getBeamColor()
    {
        return beamColor;
    }
    
    public void setBeamColor(int color)
    {
        this.beamColor = blend(new Color(beamColor), new Color(0xFF000000|color),0.20f).getRGB();
    }

    public static Color blend( Color c1, Color c2, float ratio ) {
        if ( ratio > 1f ) ratio = 1f;
        else if ( ratio < 0f ) ratio = 0f;
        float iRatio = 1.0f - ratio;

        int i1 = c1.getRGB();
        int i2 = c2.getRGB();

        int a1 = (i1 >> 24 & 0xff);
        int r1 = ((i1 & 0xff0000) >> 16);
        int g1 = ((i1 & 0xff00) >> 8);
        int b1 = (i1 & 0xff);

        int a2 = (i2 >> 24 & 0xff);
        int r2 = ((i2 & 0xff0000) >> 16);
        int g2 = ((i2 & 0xff00) >> 8);
        int b2 = (i2 & 0xff);

        int a = (int)((a1 * iRatio) + (a2 * ratio));
        int r = (int)((r1 * iRatio) + (r2 * ratio));
        int g = (int)((g1 * iRatio) + (g2 * ratio));
        int b = (int)((b1 * iRatio) + (b2 * ratio));

        return new Color( a << 24 | r << 16 | g << 8 | b );
    }
    
    @Override
    public void openInventory()
    {
    }

    @Override
    public void closeInventory()
    {
        
    }

    @Override
    public boolean isItemValidForSlot(int var1, ItemStack var2)
    {
        return var2 != null && var2.getItem() != null && var2.getItem() == ModCustomAI.rewriterItem;
    }

    /**
     * Overriden in a sign to provide the text.
     */
    public Packet getDescriptionPacket()
    {
        NBTTagCompound nbttagcompound = new NBTTagCompound();
        this.writeToNBT(nbttagcompound);
        return new S35PacketUpdateTileEntity(this.xCoord, this.yCoord, this.zCoord, 3, nbttagcompound);
    }

    @SideOnly(Side.CLIENT)
    public double getMaxRenderDistanceSquared()
    {
        return 65536.0D;
    }

    public void readFromNBT(NBTTagCompound nbt)
    {
        super.readFromNBT(nbt);
        this.beamColor = nbt.getInteger("BeamColor");
        NBTTagCompound itemNBT = nbt.getCompoundTag("Item");
        stack = ItemStack.loadItemStackFromNBT(itemNBT);
        NBTTagList targetTasksNBT = (NBTTagList) nbt.getTag("CustomAITargetTasks");
        NBTTagList tasksNBT = (NBTTagList) nbt.getTag("CustomAITasks");
        if(tasksNBT != null)
        {
            int n = tasksNBT.tagCount();
            for(int i = 0;i<n;i++)
            {
                tasks.add(new JSONObject(tasksNBT.getStringTagAt(i)));
            }
        }
        
        if(targetTasksNBT != null)
        {
            int n = targetTasksNBT.tagCount();
            for(int i = 0;i<n;i++)
            {
                targetTasks.add(new JSONObject(targetTasksNBT.getStringTagAt(i)));
            }
        }
    }

    public void writeToNBT(NBTTagCompound nbt)
    {
        super.writeToNBT(nbt);
        nbt.setInteger("BeamColor", this.beamColor);
        NBTTagCompound itemNBT = new NBTTagCompound();
        if(stack != null)
            stack.writeToNBT(itemNBT);
        nbt.setTag("Item", itemNBT);
        NBTTagList tasksNBT = new NBTTagList();
        NBTTagList targetTasksNBT = new NBTTagList();
        for(int i = 0;i<tasks.size();i++)
        {
            tasksNBT.appendTag(new NBTTagString(tasks.get(i).toString()));
        }
        for(int i = 0;i<targetTasks.size();i++)
        {
            targetTasksNBT.appendTag(new NBTTagString(targetTasks.get(i).toString()));
        }
        nbt.setTag("CustomAITasks", tasksNBT);
        nbt.setTag("CustomAITargetTasks", targetTasksNBT);
    }
    
    public void onDataPacket(NetworkManager net, S35PacketUpdateTileEntity pkt)
    {
        this.readFromNBT(pkt.func_148857_g());
    }
    
    @SideOnly(Side.CLIENT)
    public AxisAlignedBB getRenderBoundingBox()
    {
        AxisAlignedBB bb = INFINITE_EXTENT_AABB;
        return bb;
    }

    public double getRange()
    {
        return range;
    }

    public List<JSONObject> getTasks()
    {
        return tasks;
    }
    
    public List<JSONObject> getTargetTasks()
    {
        return targetTasks;
    }

    public static void registerItems(Class<? extends Entity> class1, Object[] objects)
    {
        for(Object o : objects)
        {
            if(o instanceof Item)
            {
                itemMap.put((Item)o, class1);
            }
            else if(o instanceof Block)
            {
                blockMap.put((Block)o, class1);
            }
        }
    }
} public final class EntityAIFactory
{

    private static EntityAIFactory instance;
    private static final HashMap<Class<? extends EntityAIBase>[], EntityAIWorker> workerRegistry = new HashMap<Class<? extends EntityAIBase>[], EntityAIWorker>(); 

    public static EntityAIFactory instance()
    {
        if(instance == null)
            instance = new EntityAIFactory();
        return instance;
    }

    private static EntitySheep testSheep;
    
    public JSONObject generateJSON(EntityLiving entity, EntityAITaskEntry entry)
    {
        Class<? extends EntityAIBase> clazz = entry.action.getClass();
        EntityAIWorker worker = this.getWorkerFromClass(clazz);
        if(worker == null)
            System.out.println(clazz);
        if(worker != null)
        {
            return worker.generateJSON(entity, entry);
        }
        return new JSONObject();
    }

    public EntityAITaskEntry generateAIBase(EntityLiving entity, String jsonData)
    {
        return generateAIBase(entity, new JSONObject(jsonData));
    }

    @SuppressWarnings("unchecked")
    public EntityAITaskEntry generateAIBaseWithExceptions(EntityLiving entity, JSONObject json) throws Exception
    {
        if(!json.has("type"))
            return null;
        String clazz = json.getString("type");
        Class<? extends EntityAIBase> c = null;
        if(clazz.contains("."))
        {
            c = (Class<? extends EntityAIBase>) Class.forName(clazz);
        }
        else
        {
            c = CustomAIHelper.tryToGetAITaskFromName(clazz);
            if(c == null)
                throw new CustomAIException("Couldn't find correct AI class for \""+clazz+"\"");
        }
        if(entity == null || CustomAIHelper.isSuitableForEntity(entity, c))
        {
            if(testSheep == null)
                testSheep = new EntitySheep(null);
            EntityAIWorker worker = getWorkerFromClass(c);
            if(worker != null)
            {
                EntityAITaskEntry entry = worker.generateAIBaseWithExceptions(entity, json);
                if(entry.action == null)
                    return null;
                return entry;
            }
            return null;
        }
        return null;
    }
   
    public EntityAIWorker getWorkerFromClass(Class<? extends EntityAIBase> c)
    {
        Set<Class<? extends EntityAIBase>[]> keys = workerRegistry.keySet();
        Iterator<Class<? extends EntityAIBase>[]> it = keys.iterator();
        while(it.hasNext())
        {
            Class<? extends EntityAIBase>[] classes = it.next();
            for(Class<? extends EntityAIBase> clazz : classes)
            {
                if(clazz == c)
                {
                    return workerRegistry.get(classes);
                }
            }
        }
        return null;
    }

    public EntityAITaskEntry generateAIBase(EntityLiving entity, JSONObject json)
    {
        try
        {
            return generateAIBaseWithExceptions(entity, json);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        return null;
    }

    public JSONObject createDefaultJSON(Class<? extends EntityAIBase> clazz)
    {
        EntityAIWorker worker = getWorkerFromClass(clazz);
        if(worker != null)
        {
            return worker.createDefaultJSON(clazz);
        }
        return new JSONObject();
    }
    
    public static void hireWorker(EntityAIWorker worker, Class<? extends EntityAIBase>... classes)
    {
        workerRegistry.put(classes, worker);
    }

} public class EntityAIVanillaWorker extends EntityAIWorker
{

    @SuppressWarnings("unchecked")
    @Override
    public EntityAITaskEntry generateAIBaseWithExceptions(EntityLiving entity, JSONObject json) throws Exception
    {
        if(!json.has("type"))
            return null;
        String clazz = json.getString("type");
        Class<? extends EntityAIBase> c = null;
        if(clazz.contains("."))
        {
            c = (Class<? extends EntityAIBase>) Class.forName(clazz);
        }
        else
        {
            c = CustomAIHelper.tryToGetAITaskFromName(clazz);
            if(c == null)
                throw new CustomAIException("Couldn't find correct AI class for \""+clazz+"\"");
        }
        if(entity == null || CustomAIHelper.isSuitableForEntity(entity, c))
        {
            if(testSheep == null)
                testSheep = new EntitySheep(null);
            EntityAITaskEntry entry = (entity == null ? testSheep.tasks : entity.tasks).new EntityAITaskEntry(json.getInt("priority"), null);
            if(c == EntityAISwimming.class)
            {
                entry.action = new EntityAISwimming(entity);
            }
            else if(c == EntityAICreeperSwell.class)
            {
                entry.action = new EntityAICreeperSwell((EntityCreeper) entity);
            }
            else if(c == EntityAIAvoidEntity.class)
            {
                Class<? extends Entity> clazz1 = null;
                Class<? extends Entity> entClass = getEntityClass(json.getString("Entity class name"));
                if(entClass != null)
                    clazz1 = entClass;
                else if(checkIfClassExists(clazz))
                    clazz1 = (Class<? extends Entity>) Class.forName(clazz);
                else
                    clazz1 = EntityLiving.class;
                entry.action = new EntityAIAvoidEntity((EntityCreature)entity, clazz1, (float)json.getDouble("Distance from entity"),json.getDouble("Near speed"),json.getDouble("Far speed"));
            }
            else if(c == EntityAIAttackOnCollide.class)
            {
                if(json.getString("Entity class name").equals("none") || !checkIfClassExists(json.getString("Entity class name")))
                {
                    entry.action = new EntityAIAttackOnCollide((EntityCreature)entity, json.getDouble("Speed towards target"), json.getBoolean("Long memory"));
                }
                else
                {
                    entry.action = new EntityAIAttackOnCollide((EntityCreature)entity, getEntityClass(json.getString("Entity class name")), json.getDouble("Speed towards target"), json.getBoolean("Long memory"));
                }
            }
            else if(c == EntityAIWander.class)
            {
                entry.action = new EntityAIWander((EntityCreature)entity, json.getDouble("Speed"));
            }
            else if(c == EntityAIWatchClosest.class)
            {
                Class<? extends Entity> clazz1 = null;
                Class<? extends Entity> entClass = getEntityClass(json.getString("Entity class name"));
                if(entClass != null)
                    clazz1 = entClass;
                else if(checkIfClassExists(clazz))
                    clazz1 = (Class<? extends Entity>) Class.forName(clazz);
                else
                    clazz1 = EntityLiving.class;
                entry.action = new EntityAIWatchClosest(entity, clazz1, (float) json.getDouble("Max distance"), (float)json.getDouble("Look probability"));
            }
            else if(c == EntityAILookIdle.class)
            {
                entry.action = new EntityAILookIdle(entity);
            }
            else if(c == EntityAINearestAttackableTarget.class)
            {
                String s = json.getString("Selector");
                IEntitySelector selector = null;
                if(s.equals("Mobs"))
                {
                    selector = IMob.mobSelector;
                }
                else if(s.equals("Passive animals"))
                {
                    /**
                     * From the WitherBoss
                     */
                    selector = new IEntitySelector()
                    {
                        /**
                         * Return whether the specified entity is applicable to this filter.
                         */
                        public boolean isEntityApplicable(Entity par1Entity)
                        {
                            return par1Entity instanceof EntityLivingBase && ((EntityLivingBase)par1Entity).getCreatureAttribute() != EnumCreatureAttribute.UNDEAD;
                        }
                    };
                }
                else
                {
                    selector = null;
                }
                Class<? extends Entity> clazz1 = null;
                Class<? extends Entity> entClass = getEntityClass(json.getString("Entity class name"));
                if(entClass != null)
                    clazz1 = entClass;
                else if(checkIfClassExists(clazz))
                    clazz1 = (Class<? extends Entity>) Class.forName(clazz);
                else
                    clazz1 = EntityLiving.class;
                entry.action = new EntityAINearestAttackableTarget((EntityCreature)entity, clazz1, json.getInt("Target chance"), json.getBoolean("On sight"), json.getBoolean("Nearby only"), selector);
            }
            else if(c == EntityAIHurtByTarget.class)
            {
                entry.action = new EntityAIHurtByTarget((EntityCreature) entity, json.getBoolean("Calls for help"));
            }
            else if(c == EntityAIArrowAttack.class)
            {
                entry.action = new EntityAIArrowAttack((IRangedAttackMob)entity, json.getDouble("Move speed"), json.getInt("Reload time"), json.getInt("Max reload time"), (float) json.getDouble("Distance from target"));
            }
            else if(c == EntityAIBeg.class)
            {
                entry.action = new EntityAIBeg((EntityWolf)entity, (float) json.getDouble("Min distance"));
            }
            else if(c == EntityAIBreakDoor.class)
            {
                entry.action = new EntityAIBreakDoor(entity);
            }
            else if(c == EntityAIControlledByPlayer.class)
            {
                entry.action = new EntityAIControlledByPlayer(entity, (float)json.getDouble("Max speed"));
            }
            else if(c == EntityAIDefendVillage.class)
            {
                entry.action = new EntityAIDefendVillage((EntityIronGolem)entity);
            }
            else if(c == EntityAIEatGrass.class)
            {
                entry.action = new EntityAIEatGrass(entity);
            }
            else if(c == EntityAIFleeSun.class)
            {
                entry.action = new EntityAIFleeSun((EntityCreature)entity, json.getDouble("Move speed"));
            }
            else if(c == EntityAIFollowGolem.class)
            {
                entry.action = new EntityAIFollowGolem((EntityVillager)entity);
            }
            else if(c == EntityAIFollowOwner.class)
            {
                entry.action = new EntityAIFollowOwner((EntityTameable)entity,json.getDouble("Move speed"),(float)json.getDouble("Min distance"),(float)json.getDouble("Max distance"));
            }
            else if(c == EntityAIFollowParent.class)
            {
                entry.action = new EntityAIFollowParent((EntityAnimal)entity, json.getDouble("Move speed"));
            }
            else if(c == EntityAILeapAtTarget.class)
            {
                entry.action = new EntityAILeapAtTarget(entity, (float)json.getDouble("Motion Y modifier"));
            }
            else if(c == EntityAILookAtTradePlayer.class)
            {
                entry.action = new EntityAILookAtTradePlayer((EntityVillager) entity);
            }
            else if(c == EntityAILookAtVillager.class)
            {
                entry.action = new EntityAILookAtVillager((EntityIronGolem)entity);
            }
            else if(c == EntityAIMate.class)
            {
                entry.action = new EntityAIMate((EntityAnimal) entity, json.getDouble("Move speed"));
            }
            else if(c == EntityAIPanic.class)
            {
                entry.action = new EntityAIPanic((EntityCreature) entity, json.getDouble("Move speed"));
            }
            else if(c == EntityAITempt.class)
            {
                entry.action = new EntityAITempt((EntityCreature)entity, json.getDouble("Move speed"), (Item)Item.itemRegistry.getObject(json.getString("Item name")), json.getBoolean("Scared by player's movements"));
            }
            else if(c == EntityAIMoveIndoors.class)
            {
                entry.action = new EntityAIMoveIndoors((EntityCreature)entity);
            }
            else if(c == EntityAIMoveThroughVillage.class)
            {
                entry.action = new EntityAIMoveThroughVillage((EntityCreature)entity, json.getDouble("Move speed"), json.getBoolean("Nocturnal"));
            }
            else if(c == EntityAIMoveTowardsRestriction.class)
            {
                entry.action = new EntityAIMoveTowardsRestriction((EntityCreature)entity, json.getDouble("Move speed"));
            }
            else if(c == EntityAIMoveTowardsTarget.class)
            {
                entry.action = new EntityAIMoveTowardsTarget((EntityCreature)entity, json.getDouble("Move speed"), (float)json.getDouble("Max distance"));
            }
            else if(c == EntityAIOcelotAttack.class)
            {
                entry.action = new EntityAIOcelotAttack(entity);
            }
            else if(c == EntityAIOcelotSit.class)
            {
                entry.action = new EntityAIOcelotSit((EntityOcelot)entity, json.getDouble("Move speed"));
            }
            else if(c == EntityAIPlay.class)
            {
                entry.action = new EntityAIPlay((EntityVillager)entity, json.getDouble("Move speed"));
            }
            else if(c == EntityAIRestrictOpenDoor.class)
            {
                entry.action = new EntityAIRestrictOpenDoor((EntityCreature)entity);
            }
            else if(c == EntityAIRestrictSun.class)
            {
                entry.action = new EntityAIRestrictSun((EntityCreature)entity);
            }
            else if(c == EntityAIRunAroundLikeCrazy.class)
            {
                entry.action = new EntityAIRunAroundLikeCrazy((EntityHorse)entity, json.getDouble("Move speed"));
            }
            else if(c == EntityAISit.class)
            {
                entry.action = new EntityAISit((EntityTameable)entity);
            }
            else if(c == EntityAITargetNonTamed.class)
            {
                Class<? extends Entity> clazz1 = null;
                Class<? extends Entity> entClass = getEntityClass(json.getString("Entity class name"));
                if(entClass != null)
                    clazz1 = entClass;
                else if(checkIfClassExists(clazz))
                    clazz1 = (Class<? extends Entity>) Class.forName(clazz);
                else
                    clazz1 = EntityLiving.class;
                entry.action = new EntityAITargetNonTamed((EntityTameable)entity, clazz1, json.getInt("Target chance"), json.getBoolean("On sight"));
            }
            else if(c == EntityAITradePlayer.class)
            {
                entry.action = new EntityAITradePlayer((EntityVillager)entity);
            }
            else if(c == EntityAIVillagerMate.class)
            {
                entry.action = new EntityAIVillagerMate((EntityVillager)entity);
            }
            else if(c == EntityAIWatchClosest2.class)
            {
                Class<? extends Entity> clazz1 = null;
                Class<? extends Entity> entClass = getEntityClass(json.getString("Entity class name"));
                if(entClass != null)
                    clazz1 = entClass;
                else if(checkIfClassExists(clazz))
                    clazz1 = (Class<? extends Entity>) Class.forName(clazz);
                else
                    clazz1 = EntityLiving.class;
                entry.action = new EntityAIWatchClosest2(entity, clazz1, (float) json.getDouble("Max distance"), (float)json.getDouble("Look probability"));
            }
            else if(c == EntityAIOpenDoor.class)
            {
                entry.action = new EntityAIOpenDoor(entity, json.getBoolean("Open"));
            }
            else if(c == EntityAIOwnerHurtByTarget.class)
            {
                entry.action = new EntityAIOwnerHurtByTarget((EntityTameable)entity);
            }
            else if(c == EntityAIOwnerHurtTarget.class)
            {
                entry.action = new EntityAIOwnerHurtTarget((EntityTameable)entity);
            }
            
            return entry;
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    @Override
    public JSONObject generateJSON(EntityLiving entity, EntityAITaskEntry entry)
    {
        if(entity == null)
            entity = EntityAIWorker.testSheep;
        if(entity == null)
            entity = new EntitySheep(null);
        JSONObject object = new JSONObject();
        if(entry == null || entry.action == null)
            return object;
        object.put("type", entry.action.getClass().getCanonicalName());
        object.put("priority", entry.priority);
        
        Class<? extends EntityAIBase> clazz = entry.action.getClass();
        if(clazz == EntityAIAvoidEntity.class)
        {
            Class<? extends Entity> entityToAvoid = ObfuscationReflectionHelper.getPrivateValue(EntityAIAvoidEntity.class, (EntityAIAvoidEntity)entry.action, 8);
            object.put("Entity class name", entityToAvoid == null ? "none" : getEntityName(entityToAvoid));
            object.put("Distance from entity", (Float)ObfuscationReflectionHelper.getPrivateValue(EntityAIAvoidEntity.class, (EntityAIAvoidEntity)entry.action, 5));
            object.put("Near speed", (Double)ObfuscationReflectionHelper.getPrivateValue(EntityAIAvoidEntity.class, (EntityAIAvoidEntity)entry.action, 3));
            object.put("Far speed", (Double)ObfuscationReflectionHelper.getPrivateValue(EntityAIAvoidEntity.class, (EntityAIAvoidEntity)entry.action, 2));
        }
        else if(clazz == EntityAIAttackOnCollide.class)
        {
            Class<? extends Entity> entityToAvoid = ObfuscationReflectionHelper.getPrivateValue(EntityAIAttackOnCollide.class, (EntityAIAttackOnCollide)entry.action, 6);
            object.put("Entity class name", entityToAvoid == null ? "none" : getEntityName(entityToAvoid));
            object.put("Speed towards target", (Double)ObfuscationReflectionHelper.getPrivateValue(EntityAIAttackOnCollide.class, (EntityAIAttackOnCollide)entry.action, 3));
            object.put("Long memory", (Boolean)ObfuscationReflectionHelper.getPrivateValue(EntityAIAttackOnCollide.class, (EntityAIAttackOnCollide)entry.action, 4));
        }
        else if(clazz == EntityAIWander.class)
        {
            object.put("Speed", (Double)ObfuscationReflectionHelper.getPrivateValue(EntityAIWander.class, (EntityAIWander)entry.action, 4));
        }
        else if(clazz == EntityAIWatchClosest.class || clazz == EntityAIWatchClosest2.class)
        {
            object.put("Entity class name", getEntityName(((Class<? extends Entity>)ObfuscationReflectionHelper.getPrivateValue(EntityAIWatchClosest.class, (EntityAIWatchClosest)entry.action, 5))));
            object.put("Max distance", (Float)ObfuscationReflectionHelper.getPrivateValue(EntityAIWatchClosest.class, (EntityAIWatchClosest)entry.action, 2));
            object.put("Look probability", (Float)ObfuscationReflectionHelper.getPrivateValue(EntityAIWatchClosest.class, (EntityAIWatchClosest)entry.action, 4));
        }
        else if(clazz == EntityAINearestAttackableTarget.class)
        {
            object.put("Entity class name", getEntityName(((Class<? extends Entity>)ObfuscationReflectionHelper.getPrivateValue(EntityAINearestAttackableTarget.class, (EntityAINearestAttackableTarget)entry.action, 0))));
            object.put("Target chance", (Integer)ObfuscationReflectionHelper.getPrivateValue(EntityAINearestAttackableTarget.class, (EntityAINearestAttackableTarget)entry.action, 1));
            IEntitySelector selector = ObfuscationReflectionHelper.getPrivateValue(EntityAINearestAttackableTarget.class, (EntityAINearestAttackableTarget)entry.action, 3);
            object.put("Selector", testForSelector(entity, selector));
            object.put("On sight", (Boolean)ObfuscationReflectionHelper.getPrivateValue(EntityAITarget.class, (EntityAINearestAttackableTarget)entry.action, 1));
            object.put("Nearby only", (Boolean)ObfuscationReflectionHelper.getPrivateValue(EntityAITarget.class, (EntityAINearestAttackableTarget)entry.action, 2));
        }
        else if(clazz == EntityAIHurtByTarget.class)
        {
            object.put("Calls for help", (Boolean)ObfuscationReflectionHelper.getPrivateValue(EntityAIHurtByTarget.class, (EntityAIHurtByTarget)entry.action, 0));
        }
        else if(clazz == EntityAIArrowAttack.class)
        {
            object.put("Move speed", (Double)ObfuscationReflectionHelper.getPrivateValue(EntityAIArrowAttack.class, (EntityAIArrowAttack)entry.action, 4));
            object.put("Reload time", (Integer)ObfuscationReflectionHelper.getPrivateValue(EntityAIArrowAttack.class, (EntityAIArrowAttack)entry.action, 6));
            object.put("Max reload time", (Integer)ObfuscationReflectionHelper.getPrivateValue(EntityAIArrowAttack.class, (EntityAIArrowAttack)entry.action, 7));
            object.put("Distance from target", (Float)ObfuscationReflectionHelper.getPrivateValue(EntityAIArrowAttack.class, (EntityAIArrowAttack)entry.action, 8));
        }
        else if(clazz == EntityAIBeg.class)
        {
            object.put("Min distance", (Float)ObfuscationReflectionHelper.getPrivateValue(EntityAIBeg.class, (EntityAIBeg)entry.action, 3));
        }
        else if(clazz == EntityAIControlledByPlayer.class)
        {
            object.put("Max speed", (Float)ObfuscationReflectionHelper.getPrivateValue(EntityAIControlledByPlayer.class, (EntityAIControlledByPlayer)entry.action, 1));
        }
        else if(clazz == EntityAIFleeSun.class)
        {
            object.put("Move speed", (Double)ObfuscationReflectionHelper.getPrivateValue(EntityAIFleeSun.class, (EntityAIFleeSun)entry.action, 4));
        }
        else if(clazz == EntityAIFollowOwner.class)
        {
            object.put("Move speed", (Double)ObfuscationReflectionHelper.getPrivateValue(EntityAIFollowOwner.class, (EntityAIFollowOwner)entry.action, 3));
            object.put("Min distance", (Float)ObfuscationReflectionHelper.getPrivateValue(EntityAIFollowOwner.class, (EntityAIFollowOwner)entry.action, 7));
            object.put("Max distance", (Float)ObfuscationReflectionHelper.getPrivateValue(EntityAIFollowOwner.class, (EntityAIFollowOwner)entry.action, 6));
        }
        else if(clazz == EntityAIFollowParent.class)
        {
            object.put("Move speed", (Double)ObfuscationReflectionHelper.getPrivateValue(EntityAIFollowParent.class, (EntityAIFollowParent)entry.action, 2));
        }
        else if(clazz == EntityAILeapAtTarget.class)
        {
            object.put("Motion Y modifier", (Float)ObfuscationReflectionHelper.getPrivateValue(EntityAILeapAtTarget.class, (EntityAILeapAtTarget)entry.action,2));
        }
        else if(clazz == EntityAIMate.class)
        {
            object.put("Move speed", (Double)ObfuscationReflectionHelper.getPrivateValue(EntityAIMate.class, (EntityAIMate)entry.action, 4));
        }
        else if(clazz == EntityAIPanic.class)
        {
            object.put("Move speed", (Double)ObfuscationReflectionHelper.getPrivateValue(EntityAIPanic.class, (EntityAIPanic)entry.action, 1));
        }
        else if(clazz == EntityAITempt.class)
        {
            object.put("Move speed", (Double)ObfuscationReflectionHelper.getPrivateValue(EntityAITempt.class, (EntityAITempt)entry.action, 1));
            Item item = ((Item)ObfuscationReflectionHelper.getPrivateValue(EntityAITempt.class, (EntityAITempt)entry.action, 10));
            String s = Item.itemRegistry.getNameForObject(item);
            object.put("Item name", s);
            object.put("Scared by player's movements", (Boolean)ObfuscationReflectionHelper.getPrivateValue(EntityAITempt.class, (EntityAITempt)entry.action, 11));
        }
        else if(clazz == EntityAIMoveThroughVillage.class)
        {
            object.put("Move speed", (Double)ObfuscationReflectionHelper.getPrivateValue(EntityAIMoveThroughVillage.class, (EntityAIMoveThroughVillage)entry.action, 1));
            object.put("Nocturnal", (Boolean)ObfuscationReflectionHelper.getPrivateValue(EntityAIMoveThroughVillage.class, (EntityAIMoveThroughVillage)entry.action, 4));
        }
        else if(clazz == EntityAIMoveTowardsRestriction.class)
        {
            object.put("Move speed", (Double)ObfuscationReflectionHelper.getPrivateValue(EntityAIMoveTowardsRestriction.class, (EntityAIMoveTowardsRestriction)entry.action, 4));
        }
        else if(clazz == EntityAIMoveTowardsTarget.class)
        {
            object.put("Move speed", (Double)ObfuscationReflectionHelper.getPrivateValue(EntityAIMoveTowardsTarget.class, (EntityAIMoveTowardsTarget)entry.action, 5));
        }
        else if(clazz == EntityAIOcelotSit.class)
        {
            object.put("Move speed", (Double)ObfuscationReflectionHelper.getPrivateValue(EntityAIOcelotSit.class, (EntityAIOcelotSit)entry.action, 1));
        }
        else if(clazz == EntityAIPlay.class)
        {
            object.put("Move speed", (Double)ObfuscationReflectionHelper.getPrivateValue(EntityAIPlay.class, (EntityAIPlay)entry.action, 2));   
        }
        else if(clazz == EntityAIRunAroundLikeCrazy.class)
        {
            object.put("Move speed", (Double)ObfuscationReflectionHelper.getPrivateValue(EntityAIRunAroundLikeCrazy.class, (EntityAIRunAroundLikeCrazy)entry.action, 1));
        }
        else if(clazz == EntityAITargetNonTamed.class)
        {
            object.put("Entity class name", getEntityName(((Class<? extends Entity>)ObfuscationReflectionHelper.getPrivateValue(EntityAINearestAttackableTarget.class, (EntityAITargetNonTamed)entry.action, 0))));
            object.put("Target chance", (Integer)ObfuscationReflectionHelper.getPrivateValue(EntityAINearestAttackableTarget.class, (EntityAINearestAttackableTarget)entry.action, 1));
            object.put("On sight", (Boolean)ObfuscationReflectionHelper.getPrivateValue(EntityAITarget.class, (EntityAITarget)entry.action, 1));
        }
        else if(clazz == EntityAIOpenDoor.class)
        {
            object.put("Open", (Boolean)ObfuscationReflectionHelper.getPrivateValue(EntityAIOpenDoor.class, (EntityAIOpenDoor)entry.action, 0));
        }
        return object;
    }

    @Override
    public JSONObject createDefaultJSON(Class<? extends EntityAIBase> clazz)
    {
        JSONObject object = new JSONObject();
        object.put("type", clazz.getCanonicalName());
        object.put("priority", 1);
        if(clazz == EntityAIAvoidEntity.class)
        {
            object.put("Entity class name", "none");
            object.put("Distance from entity", 10f);
            object.put("Near speed", 1.2);
            object.put("Far speed", 1.2);
        }
        else if(clazz == EntityAIAttackOnCollide.class)
        {
            object.put("Entity class name", "none");
            object.put("Speed towards target", 1);
            object.put("Long memory", false);
        }
        else if(clazz == EntityAIWander.class)
        {
            object.put("Speed", 0.8);
        }
        else if(clazz == EntityAIWatchClosest.class)
        {
            object.put("Entity class name", "none");
            object.put("Max distance", 10f);
            object.put("Look probability", 0.5f);
        }
        else if(clazz == EntityAINearestAttackableTarget.class)
        {
            object.put("Entity class name", "Mob");
            object.put("Target chance", 10);
            object.put("Selector", "UNKNOWN");
            object.put("On sight", false);
            object.put("Nearby only", true);
        }
        else if(clazz == EntityAIHurtByTarget.class)
        {
            object.put("Calls for help", false);
        }
        else if(clazz == EntityAIArrowAttack.class)
        {
            object.put("Move speed", 0.9);
            object.put("Reload time", 20);
            object.put("Max reload time", 60);
            object.put("Distance from target", 10f);
        }
        else if(clazz == EntityAIBeg.class)
        {
            object.put("Min distance", 6f);
        }
        else if(clazz == EntityAIControlledByPlayer.class)
        {
            object.put("Max speed", 1.5f);
        }
        else if(clazz == EntityAIFleeSun.class)
        {
            object.put("Move speed", 1.1);
        }
        else if(clazz == EntityAIFollowOwner.class)
        {
            object.put("Move speed", 0.9);
            object.put("Min distance", 2f);
            object.put("Max distance", 15f);
        }
        else if(clazz == EntityAIFollowParent.class)
        {
            object.put("Move speed", 0.9);
        }
        else if(clazz == EntityAILeapAtTarget.class)
        {
            object.put("Motion Y modifier", 0.5f);
        }
        else if(clazz == EntityAIMate.class)
        {
            object.put("Move speed", 0.9);
        }
        else if(clazz == EntityAIPanic.class)
        {
            object.put("Move speed", 0.9);
        }
        else if(clazz == EntityAITempt.class)
        {
            object.put("Move speed", 0.9);
            object.put("Item name", "wheat");
            object.put("Scared by player's movements", false);
        }
        else if(clazz == EntityAIMoveThroughVillage.class)
        {
            object.put("Move speed", 0.9);
            object.put("Nocturnal", false);
        }
        else if(clazz == EntityAIMoveTowardsRestriction.class)
        {
            object.put("Move speed", 0.9);
        }
        else if(clazz == EntityAIMoveTowardsTarget.class)
        {
            object.put("Move speed", 0.9);
        }
        else if(clazz == EntityAIOcelotSit.class)
        {
            object.put("Move speed", 0.9);
        }
        else if(clazz == EntityAIPlay.class)
        {
            object.put("Move speed", 0.9);   
        }
        else if(clazz == EntityAIRunAroundLikeCrazy.class)
        {
            object.put("Move speed", 0.9);
        }
        else if(clazz == EntityAITargetNonTamed.class)
        {
            object.put("Entity class name", "none");
            object.put("Target chance", 10);
            object.put("On sight", false);
        }
        else if(clazz == EntityAIOpenDoor.class)
        {
            object.put("Open", true);
        }
        else if(clazz == EntityAIWatchClosest2.class)
        {
            object.put("Max distance", 15);
            object.put("Look probability", 0.5);
            object.put("Entity class name", "Mob");
        }
        return object;
    }

    @Override
    public boolean isSuitableForEntity(EntityLiving entity, Class<? extends EntityAIBase> c)
    {
        if(c == EntityAICreeperSwell.class && !(entity instanceof EntityCreeper))
            return false;
        else if(c == EntityAIAvoidEntity.class && !(entity instanceof EntityCreature))
            return false;
        else if(c == EntityAIAttackOnCollide.class && !(entity instanceof EntityCreature))
            return false;
        else if(c == EntityAIWander.class && !(entity instanceof EntityCreature))
            return false;
        else if(c == EntityAINearestAttackableTarget.class && !(entity instanceof EntityCreature))
            return false;
        else if(c == EntityAIHurtByTarget.class && !(entity instanceof EntityCreature))
            return false;
        else if(c == EntityAIArrowAttack.class && !(entity instanceof IRangedAttackMob))
            return false;
        else if(c == EntityAIBeg.class && !(entity instanceof EntityWolf))
            return false;
        else if(c == EntityAIDefendVillage.class && !(entity instanceof EntityIronGolem))
            return false;
        else if(c == EntityAIFleeSun.class && !(entity instanceof EntityCreature))
            return false;
        else if(c == EntityAIFollowGolem.class && !(entity instanceof EntityVillager))
            return false;
        else if(c == EntityAIFollowOwner.class && !(entity instanceof EntityTameable))
            return false;
        else if(c == EntityAIFollowParent.class && !(entity instanceof EntityAnimal))
            return false;
        else if(c == EntityAILookAtTradePlayer.class && !(entity instanceof EntityVillager))
            return false;
        else if(c == EntityAILookAtVillager.class && !(entity instanceof EntityIronGolem))
            return false;
        else if(c == EntityAIMate.class && !(entity instanceof EntityAnimal))
            return false;
        else if(c == EntityAIPanic.class && !(entity instanceof EntityCreature))
            return false;
        else if(c == EntityAITempt.class && !(entity instanceof EntityCreature))
            return false;
        else if(c == EntityAIMoveIndoors.class && !(entity instanceof EntityCreature))
            return false;
        else if(c == EntityAIMoveThroughVillage.class && !(entity instanceof EntityCreature))
            return false;
        else if(c == EntityAIOcelotSit.class && !(entity instanceof EntityOcelot))
            return false;
        else if(c == EntityAIPlay.class && !(entity instanceof EntityVillager))
            return false;
        else if(c == EntityAIRestrictOpenDoor.class && !(entity instanceof EntityCreature))
            return false;
        else if(c == EntityAIRestrictSun.class && !(entity instanceof EntityCreature))
            return false;
        else if(c == EntityAIRunAroundLikeCrazy.class && !(entity instanceof EntityHorse))
            return false;
        else if(c == EntityAISit.class && !(entity instanceof EntityTameable))
            return false;
        else if(c == EntityAITargetNonTamed.class && !(entity instanceof EntityTameable))
            return false;
        else if(c == EntityAITradePlayer.class && !(entity instanceof EntityVillager))
            return false;
        else if(c == EntityAIVillagerMate.class && !(entity instanceof EntityVillager))
            return false;
        else if(c == EntityAIOwnerHurtTarget.class && !(entity instanceof EntityTameable))
            return false;
        else if(c == EntityAIOwnerHurtByTarget.class && !(entity instanceof EntityTameable))
            return false;
        return true;
    }

} public class EntityCustomAIAddedWorker extends EntityAIWorker
{

    @SuppressWarnings("unchecked")
    @Override
    public EntityAITaskEntry generateAIBaseWithExceptions(EntityLiving entity, JSONObject json) throws Exception
    {
        if(!json.has("type"))
            return null;
        String clazz = json.getString("type");
        Class<? extends EntityAIBase> c = null;
        if(clazz.contains("."))
        {
            c = (Class<? extends EntityAIBase>) Class.forName(clazz);
        }
        else
        {
            c = CustomAIHelper.tryToGetAITaskFromName(clazz);
            if(c == null)
                throw new CustomAIException("Couldn't find correct AI class for \""+clazz+"\"");
        }
        if(entity == null || CustomAIHelper.isSuitableForEntity(entity, c))
        {
            if(testSheep == null)
                testSheep = new EntitySheep(null);
            EntityAITaskEntry entry = (entity == null ? testSheep.tasks : entity.tasks).new EntityAITaskEntry(json.getInt("priority"), null);
            if(c == EntityAIFleeSunEvenNotBurning.class)
            {
                entry.action = new EntityAIFleeSunEvenNotBurning((EntityCreature)entity, json.getDouble("Move speed"));
            }
            else if(c == EntityAIFollowEntity.class)
            {
                Class<? extends Entity> clazz1 = null;
                Class<? extends Entity> entClass = getEntityClass(json.getString("Entity class name"));
                if(entClass != null)
                    clazz1 = entClass;
                else if(checkIfClassExists(clazz))
                    clazz1 = (Class<? extends Entity>) Class.forName(clazz);
                else
                    clazz1 = EntityLiving.class;
                entry.action = new EntityAIFollowEntity(entity, clazz1, json.getDouble("Move speed"), (float)json.getDouble("Max distance"));
            }
            else if(c == EntityAITeleportRandomly.class)
            {
                entry.action = new EntityAITeleportRandomly(entity, json.getInt("Tick rate"), json.getBoolean("Play teleport sound"), json.getBoolean("Avoid liquids"));
            }
            else if(c == EntityAIRangeAttack.class)
            {
                entry.action = new EntityAIRangeAttack(entity, json.getDouble("Move speed"), json.getInt("Reload time"), json.getInt("Max reload time"), (float) json.getDouble("Distance from target"), Projectile.fromString(json.getString("Projectile type")));
            }
            return entry;
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    @Override
    public JSONObject generateJSON(EntityLiving entity, EntityAITaskEntry entry)
    {
        if(entity == null)
            entity = testSheep;
        if(entity == null)
            entity = new EntitySheep(null);
        JSONObject object = new JSONObject();
        if(entry == null || entry.action == null)
            return object;
        object.put("type", entry.action.getClass().getCanonicalName());
        object.put("priority", entry.priority);
        
        Class<? extends EntityAIBase> clazz = entry.action.getClass();
        if(clazz == EntityAIFleeSunEvenNotBurning.class)
        {
            object.put("Move speed", (Double)ObfuscationReflectionHelper.getPrivateValue(EntityAIFleeSunEvenNotBurning.class, (EntityAIFleeSunEvenNotBurning)entry.action, 4));
        }
        else if(clazz == EntityAIFollowEntity.class)
        {
            object.put("Entity class name", getEntityName(((Class<? extends Entity>)ObfuscationReflectionHelper.getPrivateValue(EntityAIFollowEntity.class, (EntityAIFollowEntity)entry.action, 0))));
            object.put("Move speed", (Double)ObfuscationReflectionHelper.getPrivateValue(EntityAIFollowEntity.class, (EntityAIFollowEntity)entry.action, 3));
            object.put("Max distance", (Float)ObfuscationReflectionHelper.getPrivateValue(EntityAIFollowEntity.class, (EntityAIFollowEntity)entry.action, 4));
        }
        else if(clazz == EntityAITeleportRandomly.class)
        {
            object.put("Tick rate", (Integer)ObfuscationReflectionHelper.getPrivateValue(EntityAITeleportRandomly.class, (EntityAITeleportRandomly)entry.action, 1));
            object.put("Play teleport sound", (Boolean)ObfuscationReflectionHelper.getPrivateValue(EntityAITeleportRandomly.class, (EntityAITeleportRandomly)entry.action, 4));
            object.put("Avoid liquids", (Boolean)ObfuscationReflectionHelper.getPrivateValue(EntityAITeleportRandomly.class, (EntityAITeleportRandomly)entry.action, 5));
        }
        else if(clazz == EntityAIRangeAttack.class)
        {
            object.put("Move speed", (Double)ObfuscationReflectionHelper.getPrivateValue(EntityAIRangeAttack.class, (EntityAIRangeAttack)entry.action, 3));
            object.put("Reload time", (Integer)ObfuscationReflectionHelper.getPrivateValue(EntityAIRangeAttack.class, (EntityAIRangeAttack)entry.action, 5));
            object.put("Max reload time", (Integer)ObfuscationReflectionHelper.getPrivateValue(EntityAIRangeAttack.class, (EntityAIRangeAttack)entry.action, 6));
            object.put("Distance from target", (Float)ObfuscationReflectionHelper.getPrivateValue(EntityAIRangeAttack.class, (EntityAIRangeAttack)entry.action, 7));
            object.put("Projectile type", ((Projectile)ObfuscationReflectionHelper.getPrivateValue(EntityAIRangeAttack.class, (EntityAIRangeAttack)entry.action, 9)).getID());
        }
        return object;
    }

    @Override
    public JSONObject createDefaultJSON(Class<? extends EntityAIBase> clazz)
    {
        JSONObject object = new JSONObject();
        object.put("type", clazz.getCanonicalName());
        object.put("priority", 1);
        if(clazz == EntityAIFleeSunEvenNotBurning.class)
        {
            object.put("Move speed", 0.9);
        }
        else if(clazz == EntityAIFollowEntity.class)
        {
            object.put("Entity class name", "none");
            object.put("Move speed", 0.9);
            object.put("Max distance", 10f);
        }
        else if(clazz == EntityAITeleportRandomly.class)
        {
            object.put("Tick rate", 60);
            object.put("Play teleport sound", true);
            object.put("Avoid liquids", true);
        }
        else if(clazz == EntityAIRangeAttack.class)
        {
            object.put("Move speed", 0.9);
            object.put("Reload time", 20);
            object.put("Max reload time", 60);
            object.put("Distance from target", 10f);
            object.put("Projectile type", "Arrows");
        }
        return object;
    }

    @Override
    public boolean isSuitableForEntity(EntityLiving living,
            Class<? extends EntityAIBase> clazz)
    {
        if(clazz == EntityAIFleeSunEvenNotBurning.class && ! (living instanceof EntityCreature))
            return false;
        return true;
    }

} public class AICopierItem extends Item
{

    private IIcon emptyIcon;
    private IIcon usedIcon;

    public AICopierItem()
    {
        this.setUnlocalizedName("ai_copier");
        setCreativeTab(CreativeTabs.tabTools);
    }
    
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public void addInformation(ItemStack stack, EntityPlayer player, List list, boolean flag)
    {
        if(stack.getTagCompound() == null || !stack.getTagCompound().hasKey("CustomAITasks"))
        {
            list.add(EnumChatFormatting.ITALIC+"Empty");
            return;
        }
        NBTTagCompound nbt = stack.getTagCompound();
        NBTTagList targetTasksNBT = (NBTTagList) nbt.getTag("CustomAITargetTasks");
        NBTTagList tasksNBT = (NBTTagList) nbt.getTag("CustomAITasks");
        ArrayList<String> tasks = new ArrayList<String>();
        ArrayList<String> targetTasks = new ArrayList<String>();
        if(tasksNBT == null)
        {
            ;
        }
        else
        {
            for(int i = 0;i<tasksNBT.tagCount();i++)
            {
                try
                {
                    JSONObject o = new JSONObject(tasksNBT.getStringTagAt(i));
                    if(o.has("type"))
                    {
                        tasks.add(CustomAIHelper.getNameFromClass((Class<? extends EntityAIBase>) Class.forName(o.getString("type"))));
                    }
                }
                catch(Exception e)
                {
                    ;
                }
            }
        }
        if(targetTasksNBT == null)
        {
            ;
        }
        else
        {
            for(int i = 0;i<targetTasksNBT.tagCount();i++)
            {
                try
                {
                    JSONObject o = new JSONObject(targetTasksNBT.getStringTagAt(i));
                    if(o.has("type"))
                    {
                        targetTasks.add(CustomAIHelper.getNameFromClass((Class<? extends EntityAIBase>) Class.forName(o.getString("type"))));
                    }
                }
                catch(Exception e)
                {
                    ;
                }
            }
        }
        
        list.add((flag ? EnumChatFormatting.BOLD : "")+"Tasks: "+tasks.size());
        if(flag)
        {
            for(String task : tasks)
            {
                list.add("   -"+EnumChatFormatting.ITALIC+task);
            }
        }
        list.add((flag ? EnumChatFormatting.BOLD : "")+"Target Tasks: "+targetTasks.size());
        if(flag)
        {
            for(String task : targetTasks)
            {
                list.add("   -"+EnumChatFormatting.ITALIC+task);
            }
        }
    }
    
    private void applyFromStackToEntity(ItemStack stack, Entity entity, EntityPlayer entityPlayer)
    {
        NBTTagCompound nbt = stack.getTagCompound();
        NBTTagList targetTasksNBT = (NBTTagList) nbt.getTag("CustomAITargetTasks");
        NBTTagList tasksNBT = (NBTTagList) nbt.getTag("CustomAITasks");
        if(entity != null)
        {
            if(tasksNBT == null)
            {
                ;
            }
            else
            {
                CustomAIHelper.getTasksList(entity).clear();
                for(int i = 0;i<tasksNBT.tagCount();i++)
                {
                    try
                    {
                        EntityAITaskEntry e = EntityAIFactory.instance().generateAIBaseWithExceptions((EntityLiving) entity, new JSONObject(tasksNBT.getStringTagAt(i)));
                        if(e != null && e.action != null)
                            CustomAIHelper.getTasksList(entity).add(e);
                    }
                    catch(Exception e)
                    {
                        ;
                    }
                }
            }
            if(targetTasksNBT == null)
            {
                ;
            }
            else
            {
                CustomAIHelper.getTargetTasksList(entity).clear();
                for(int i = 0;i<targetTasksNBT.tagCount();i++)
                {
                    try
                    {
                        EntityAITaskEntry e = CustomAIHelper.generateAIFromJSON(entity, targetTasksNBT.getStringTagAt(i));
                        if(e != null && e.action != null)
                            CustomAIHelper.getTargetTasksList(entity).add(e);
                    }
                    catch(Exception e)
                    {
                        ;
                    }
                }
            }
        }
        entityPlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.ITALIC+"Successfully transfered AI data to the entity"));
        
        ModCustomAI.packetPipeline.sendToAll(new PacketUpdateAI(entity.getEntityId(), CustomAIHelper.getTasksList(entity), CustomAIHelper.getTargetTasksList(entity)));
    }
    
    private void copyFromCommandBlock(NBTTagCompound nbt, ItemStack stack, TileEntityCommandBlock commandBlock, EntityPlayer player)
    {
        NBTTagCompound tmp = new NBTTagCompound();
        commandBlock.writeToNBT(tmp);
        String command = tmp.getString("Command");
        while(!command.isEmpty() && command.startsWith(" "))
            command = command.substring(0);
        String[] args = command.split(" ");
        if(!args[0].startsWith("summon") && !args[0].startsWith("/summon"))
        {
            player.addChatMessage(new ChatComponentText(EnumChatFormatting.RED+"The command provided isn't a summon command!"));
            return;
        }
        if(args.length >= 5)
        {
            try
            {
                IChatComponent ichatcomponent = CommandSummon.func_147178_a(commandBlock.func_145993_a(), args, 5);
    
                NBTBase nbtbase = JsonToNBT.func_150315_a(ichatcomponent.getUnformattedText());
                if(!(nbtbase instanceof NBTTagCompound))
                {
                    player.addChatMessage(new ChatComponentText(EnumChatFormatting.RED+"Can't parse NBT data!"));
                    return;
                }
                NBTTagCompound compound = (NBTTagCompound)nbtbase;
                NBTTagList tasksList = compound.hasKey("CustomAITasks") ? (NBTTagList) compound.getTag("CustomAITasks") : null;
                NBTTagList targetTasksList = compound.hasKey("CustomAITargetTasks") ?  (NBTTagList) compound.getTag("CustomAITargetTasks") : null;
                
                if(tasksList != null)
                    nbt.setTag("CustomAITasks", tasksList);
                if(targetTasksList != null)
                    nbt.setTag("CustomAITargetTasks", targetTasksList);
                
                player.addChatMessage(new ChatComponentText(EnumChatFormatting.ITALIC+"Successfully transfered AI data from the command block"));
                stack.setItemDamage(1);
            }
            catch(Exception e)
            {
                ;
            }
        }
        else
        {
            player.addChatMessage(new ChatComponentText(EnumChatFormatting.RED+"Failed to copy from command block: no AI tasks to fetch"));
        }
    }
    
    private void copyFromEmitter(NBTTagCompound nbt, ItemStack stack, TileEntityAIEmitter aiEmitter, EntityPlayer player)
    {
        NBTTagList tasksListNBT = new NBTTagList();
        NBTTagList targetTasksListNBT = new NBTTagList();
        for(JSONObject o : aiEmitter.getTasks())
        {
            tasksListNBT.appendTag(new NBTTagString(o.toString()));
        }
        for(JSONObject o : aiEmitter.getTargetTasks())
        {
            targetTasksListNBT.appendTag(new NBTTagString(o.toString()));
        }
        nbt.setTag("CustomAITasks", tasksListNBT);
        nbt.setTag("CustomAITargetTasks", targetTasksListNBT);
        stack.setItemDamage(1);
        
        player.addChatMessage(new ChatComponentText(EnumChatFormatting.ITALIC+"Successfully transfered AI data from the emitter"));
    }

    private void copyFromEntityToStack(ItemStack stack, Entity entity, EntityPlayer entityPlayer)
    {
        stack.setItemDamage(1);
        NBTTagCompound nbt = stack.getTagCompound();
        if(nbt == null)
        {
            nbt = new NBTTagCompound();
        }
        
        List<EntityAITaskEntry> targetTasks = CustomAIHelper.getTargetTasksList(entity);
        List<EntityAITaskEntry> tasks = CustomAIHelper.getTasksList(entity);
        NBTTagList targetTasksNBT = new NBTTagList();
        NBTTagList tasksNBT = new NBTTagList();
        for(EntityAITaskEntry entry : targetTasks)
        {
            JSONObject json = CustomAIHelper.generateJSONFromAI((EntityLiving) entity, entry);
            if(json != null)
            targetTasksNBT.appendTag(new NBTTagString(json.toString()));
        }
        for(EntityAITaskEntry entry : tasks)
        {
            JSONObject json = CustomAIHelper.generateJSONFromAI((EntityLiving) entity, entry);
            if(json != null)
            tasksNBT.appendTag(new NBTTagString(json.toString()));
        }
        nbt.setTag("CustomAITargetTasks", targetTasksNBT);
        nbt.setTag("CustomAITasks", tasksNBT);
        
        stack.setTagCompound(nbt);
        
        entityPlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.ITALIC+"Successfully copied AI data from the entity"));
    }

    private void copyFromSpawner(NBTTagCompound nbt, ItemStack stack, TileEntityMobSpawner mobSpawner, EntityPlayer player)
    {
        NBTTagCompound tmp = new NBTTagCompound();
        mobSpawner.writeToNBT(tmp);
        if(tmp.hasKey("SpawnData"))
        {
            NBTTagCompound data = tmp.getCompoundTag("SpawnData");
            if(data.hasKey("CustomAITasks"))
            {
                nbt.setTag("CustomAITasks", data.getTag("CustomAITasks"));
                player.addChatMessage(new ChatComponentText(EnumChatFormatting.ITALIC+"Successfully transfered AI data from the spawner"));
            }
            else
            {
                player.addChatMessage(new ChatComponentText(EnumChatFormatting.RED+"No spawn data"));
            }
            if(data.hasKey("CustomAITargetTasks"))
            {
                nbt.setTag("CustomAITargetTasks", data.getTag("CustomAITargetTasks"));
                player.addChatMessage(new ChatComponentText(EnumChatFormatting.ITALIC+"Successfully transfered AI data from the spawner"));
            }
            else
            {
                player.addChatMessage(new ChatComponentText(EnumChatFormatting.RED+"No spawn data"));
            }
        }
        else
        {
            player.addChatMessage(new ChatComponentText(EnumChatFormatting.RED+"No spawn data"));
        }
        stack.setItemDamage(1);
    }

    private void copyToCommandBlock(NBTTagCompound nbt, ItemStack stack, TileEntityCommandBlock commandBlock, EntityPlayer player)
    {
        player.addChatMessage(new ChatComponentText(EnumChatFormatting.RED+"ddd"));
        NBTTagCompound tmp = new NBTTagCompound();
        commandBlock.writeToNBT(tmp);
        String oldCommand = tmp.getString("Command");
        while(!oldCommand.isEmpty() && oldCommand.startsWith(" "))
            oldCommand = oldCommand.substring(0);
        String[] args = oldCommand.split(" ");
        if(!args[0].startsWith("summon") && !args[0].startsWith("/summon"))
        {
            player.addChatMessage(new ChatComponentText(EnumChatFormatting.RED+"The command provided isn't a summon command!"));
            return;
        }
        if(args.length >= 6)
        {
            try
            {
                IChatComponent ichatcomponent = CommandSummon.func_147178_a(commandBlock.func_145993_a(), args, 5);
    
                NBTBase nbtbase = JsonToNBT.func_150315_a(ichatcomponent.getUnformattedText());
                if(!(nbtbase instanceof NBTTagCompound))
                {
                    player.addChatMessage(new ChatComponentText(EnumChatFormatting.RED+"Can't parse NBT data!"));
                    return;
                }
                NBTTagCompound compound = (NBTTagCompound)nbtbase;
                NBTTagList tasksList = (NBTTagList) nbt.getTag("CustomAITasks");
                NBTTagList targetTasksList = (NBTTagList) nbt.getTag("CustomAITargetTasks");
                if(tasksList != null)
                {
                    compound.setTag("CustomAITasks", nbtStringListToCompoundList(tasksList));
                }
                if(targetTasksList != null)
                {
                    compound.setTag("CustomAITargetTasks", nbtStringListToCompoundList(targetTasksList));
                }
                String newCommand= oldCommand.replace(ichatcomponent.getUnformattedText(), compound.toString());
                commandBlock.func_145993_a().func_145752_a(newCommand);
                commandBlock.func_145993_a().func_145756_e();

                player.addChatMessage(new ChatComponentText(EnumChatFormatting.ITALIC+"Successfully transfered AI data to the command block"));
            }
            catch(Exception e)
            {
                player.addChatMessage(new ChatComponentText(EnumChatFormatting.RED+"Error: "+e.getMessage()));
                ;
            }
        }
        else if(args.length >= 4)
        {
            NBTTagCompound compound = new NBTTagCompound();
            NBTTagList tasksList = (NBTTagList) nbt.getTag("CustomAITasks");
            NBTTagList targetTasksList = (NBTTagList) nbt.getTag("CustomAITargetTasks");
            if(tasksList != null)
            {
                compound.setTag("CustomAITasks", nbtStringListToCompoundList(tasksList));
            }
            if(targetTasksList != null)
            {
                compound.setTag("CustomAITargetTasks", nbtStringListToCompoundList(targetTasksList));
            }
            
            String newCommand = oldCommand+" "+compound.toString();
            commandBlock.func_145993_a().func_145752_a(newCommand);
            commandBlock.func_145993_a().func_145756_e();

            player.addChatMessage(new ChatComponentText(EnumChatFormatting.ITALIC+"Successfully transfered AI data to the command block"));

        }
    }
    
    private void copyToEmitter(NBTTagCompound nbt, ItemStack stack, TileEntityAIEmitter aiEmitter, EntityPlayer player)
    {
        NBTTagList targetTasksNBT = (NBTTagList) nbt.getTag("CustomAITargetTasks");
        NBTTagList tasksNBT = (NBTTagList) nbt.getTag("CustomAITasks");
        List<JSONObject> tasks = aiEmitter.getTasks();
        List<JSONObject> targetTasks = aiEmitter.getTasks();
        tasks.clear();
        targetTasks.clear();
        if(tasksNBT != null)
        for(int index = 0;index<tasksNBT.tagCount();index++)
        {
            tasks.add(new JSONObject(tasksNBT.getStringTagAt(index)));
        }
        if(targetTasksNBT != null)
        for(int index = 0;index<targetTasksNBT.tagCount();index++)
        {
            targetTasks.add(new JSONObject(targetTasksNBT.getStringTagAt(index)));
        }
        ModCustomAI.packetPipeline.sendToAll(new PacketUpdateAIEmitter(aiEmitter.xCoord, aiEmitter.yCoord, aiEmitter.zCoord, tasks, targetTasks));
        player.addChatMessage(new ChatComponentText(EnumChatFormatting.ITALIC+"Successfully transfered AI data to the emitter"));
    }
    
    private void copyToSpawner(NBTTagCompound nbt, ItemStack stack, TileEntityMobSpawner mobSpawner, EntityPlayer player)
    {
        if(mobSpawner.func_145881_a().getRandomEntity() == null)
        {
            mobSpawner.func_145881_a().setRandomEntity(mobSpawner.func_145881_a().new WeightedRandomMinecart((NBTTagCompound) nbt.copy(), mobSpawner.func_145881_a().getEntityNameToSpawn()));
        }
        else
        {
            NBTTagCompound old = mobSpawner.func_145881_a().getRandomEntity().field_98222_b;
            if(nbt.hasKey("CustomAITasks"))
                old.setTag("CustomAITasks", nbt.getTag("CustomAITasks"));
            if(nbt.hasKey("CustomAITargetTasks"))
                old.setTag("CustomAITargetTasks", nbt.getTag("CustomAITargetTasks"));
        }
        player.addChatMessage(new ChatComponentText(EnumChatFormatting.ITALIC+"Successfully transfered AI data to the spawner"));
//        ModCustomAI.packetPipeline.sendToAll(new PacketUpdateMobSpawnerData(mobSpawner, fromNBTListToList((NBTTagList)nbt.getTag("CustomAITasks")), fromNBTListToList((NBTTagList)nbt.getTag("CustomAITargetTasks"))));
        mobSpawner.getWorldObj().markBlockForUpdate(mobSpawner.xCoord, mobSpawner.yCoord, mobSpawner.zCoord);
    }
    
    public IIcon getIcon(ItemStack stack, int pass)
    {
        if(stack.getTagCompound() == null)
        {
            stack.setTagCompound(new NBTTagCompound());
            return stack.getItemDamage() == 1 ? usedIcon : emptyIcon;
        }
        else
        {
            if(stack.getTagCompound().hasKey("CustomAITasks"))
            {
                return usedIcon;
            }
        }
        return stack.getItemDamage() == 1 ? usedIcon : emptyIcon;
    }

    public IIcon getIconFromDamage(int damage)
    {
        return damage == 1 ? usedIcon : emptyIcon;
    }

    @SuppressWarnings("unchecked")
    private NBTTagList nbtStringListToCompoundList(NBTTagList list)
    {
        NBTTagList result = new NBTTagList();
        for(int i = 0;i<list.tagCount();i++)
        {
            String s = list.getStringTagAt(i);
            NBTTagCompound compound = new NBTTagCompound();
            JSONObject object = new JSONObject(s);
            Iterator<String> keys = object.keys();
            while(keys.hasNext())
            {
                String key = keys.next();
                compound.setString(key, ""+object.get(key));
            }
            result.appendTag(compound);
        }
        return result;
    }
    
    public boolean onInteract(World w, ItemStack stack, EntityPlayer entityPlayer, Entity target)
    {
        entityPlayer.swingItem();
        if(w.isRemote)
            return CustomAIHelper.hasEntityAI(target);
        if(CustomAIHelper.hasEntityAI(target))
        {
            if(stack.getItemDamage() == 0)
            {
                copyFromEntityToStack(stack, target, entityPlayer);
            }
            else
            {
                if(entityPlayer.isSneaking())
                {
                    copyFromEntityToStack(stack, target, entityPlayer);
                }
                else
                {
                    applyFromStackToEntity(stack, target, entityPlayer);
                }
            }
            return true;
        }
        else
        {
            entityPlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED+"This entity doesn't use the new AI system. You can't change "+EnumChatFormatting.RED+"anything, sorry :("));
            return false;
        }
    }

    public boolean onItemUseFirst(ItemStack stack, EntityPlayer player, World w, int x, int y, int z, int side, float hitX, float hitY, float hitZ)
    {
        if(w.isRemote)
            return false;
        Block block = w.getBlock(x, y, z);
        NBTTagCompound nbt = stack.getTagCompound();
        if(nbt == null)
        {
            nbt = new NBTTagCompound();
            stack.setTagCompound(nbt);
        }
        if(block == ModCustomAI.aiEmitterBlock)
        {
            TileEntityAIEmitter aiEmitter = (TileEntityAIEmitter) w.getTileEntity(x, y, z);
            if(aiEmitter == null)
                return true;
            if(nbt.hasKey("CustomAITasks"))
            {
                if(player.isSneaking())
                {
                    copyFromEmitter(nbt, stack, aiEmitter, player);
                }
                else
                {
                    copyToEmitter(nbt, stack, aiEmitter, player);
                }
            }
            else
            {
                copyFromEmitter(nbt, stack, aiEmitter, player);
            }
            return true;
        }
        else if(block == Blocks.mob_spawner)
        {
            TileEntityMobSpawner mobSpawner = (TileEntityMobSpawner) w.getTileEntity(x, y, z);
            if(mobSpawner == null)
                return true;
            if(nbt.hasKey("CustomAITasks"))
            {
                if(player.isSneaking())
                {
                    copyFromSpawner(nbt, stack, mobSpawner, player);
                }
                else
                {
                    copyToSpawner(nbt, stack, mobSpawner, player);
                }
            }
            else
            {
                copyFromSpawner(nbt, stack, mobSpawner, player);
            }
            return true;
        }
        else if(block == Blocks.command_block)
        {
            TileEntityCommandBlock commandBlock = (TileEntityCommandBlock) w.getTileEntity(x, y, z);
            
            if(commandBlock == null)
                return true;
            if(nbt.hasKey("CustomAITasks"))
            {
                if(player.isSneaking())
                {
                    copyFromCommandBlock(nbt, stack, commandBlock, player);
                }
                else
                {
                    copyToCommandBlock(nbt, stack, commandBlock, player);
                }
            }
            else
            {
                copyFromCommandBlock(nbt, stack, commandBlock, player);
            }
            return true;
        }
        return true;
    }

    public void registerIcons(IIconRegister register)
    {
        emptyIcon = register.registerIcon(ModCustomAI.MODID+":copier_empty");
        usedIcon = register.registerIcon(ModCustomAI.MODID+":copier_used");
    }
} public class AwesomeAIRewriterItem extends Item
{

    public AwesomeAIRewriterItem()
    {
        super();
        this.setUnlocalizedName("ai_rewriter");
        this.setTextureName(ModCustomAI.MODID+":ai_rewriter");
        this.setCreativeTab(CreativeTabs.tabTools);
        this.setMaxStackSize(1);
    }
} public class JSONObject
{
    /**
     * JSONObject.NULL is equivalent to the value that JavaScript calls null,
     * whilst Java's null is equivalent to the value that JavaScript calls
     * undefined.
     */
    private static final class Null
    {

        /**
         * There is only intended to be a single instance of the NULL object,
         * so the clone method returns itself.
         * 
         * @return NULL.
         */
        protected final Object clone()
        {
            return this;
        }

        /**
         * A Null object is equal to the null value and to itself.
         * 
         * @param object
         *            An object to test for nullness.
         * @return true if the object parameter is the JSONObject.NULL object or
         *         null.
         */
        public boolean equals(Object object)
        {
            return object == null || object == this;
        }

        /**
         * Get the "null" string value.
         * 
         * @return The string "null".
         */
        public String toString()
        {
            return "null";
        }
    }

    /**
     * The map where the JSONObject's properties are kept.
     */
    private final Map          map;

    /**
     * It is sometimes more convenient and less ambiguous to have a
     * <code>NULL</code> object than to use Java's <code>null</code> value.
     * <code>JSONObject.NULL.equals(null)</code> returns <code>true</code>.
     * <code>JSONObject.NULL.toString()</code> returns <code>"null"</code>.
     */
    public static final Object NULL = new Null();

    /**
     * Construct an empty JSONObject.
     */
    public JSONObject()
    {
        this.map = new HashMap();
    }

    /**
     * Construct a JSONObject from a subset of another JSONObject. An array of
     * strings is used to identify the keys that should be copied. Missing keys
     * are ignored.
     * 
     * @param jo
     *            A JSONObject.
     * @param names
     *            An array of strings.
     * @throws JSONException
     * @exception JSONException
     *                If a value is a non-finite number or if a name is
     *                duplicated.
     */
    public JSONObject(JSONObject jo, String[] names)
    {
        this();
        for (int i = 0; i < names.length; i += 1)
        {
            try
            {
                this.putOnce(names[i], jo.opt(names[i]));
            }
            catch (Exception ignore)
            {
            }
        }
    }

    /**
     * Construct a JSONObject from a JSONTokener.
     * 
     * @param x
     *            A JSONTokener object containing the source string.
     * @throws JSONException
     *             If there is a syntax error in the source string or a
     *             duplicated key.
     */
    public JSONObject(JSONTokener x) throws JSONException
    {
        this();
        char c;
        String key;

        if(x.nextClean() != '{')
        {
            throw x.syntaxError("A JSONObject text must begin with '{'");
        }
        for (;;)
        {
            c = x.nextClean();
            switch (c)
                {
                    case 0:
                        throw x.syntaxError("A JSONObject text must end with '}'");
                    case '}':
                        return;
                    default:
                        x.back();
                        key = x.nextValue().toString();
                }

            // The key is followed by ':'.

            c = x.nextClean();
            if(c != ':')
            {
                throw x.syntaxError("Expected a ':' after a key");
            }
            this.putOnce(key, x.nextValue());

            // Pairs are separated by ','.

            switch (x.nextClean())
                {
                    case ';':
                    case ',':
                        if(x.nextClean() == '}')
                        {
                            return;
                        }
                        x.back();
                        break;
                    case '}':
                        return;
                    default:
                        throw x.syntaxError("Expected a ',' or '}'");
                }
        }
    }

    /**
     * Construct a JSONObject from a Map.
     * 
     * @param map
     *            A map object that can be used to initialize the contents of
     *            the JSONObject.
     * @throws JSONException
     */
    public JSONObject(Map map)
    {
        this.map = new HashMap();
        if(map != null)
        {
            Iterator i = map.entrySet().iterator();
            while (i.hasNext())
            {
                Map.Entry e = (Map.Entry) i.next();
                Object value = e.getValue();
                if(value != null)
                {
                    this.map.put(e.getKey(), wrap(value));
                }
            }
        }
    }

    /**
     * Construct a JSONObject from an Object using bean getters. It reflects on
     * all of the public methods of the object. For each of the methods with no
     * parameters and a name starting with <code>"get"</code> or
     * <code>"is"</code> followed by an uppercase letter, the method is invoked,
     * and a key and the value returned from the getter method are put into the
     * new JSONObject.
     * 
     * The key is formed by removing the <code>"get"</code> or <code>"is"</code>
     * prefix. If the second remaining character is not upper case, then the
     * first character is converted to lower case.
     * 
     * For example, if an object has a method named <code>"getName"</code>, and
     * if the result of calling <code>object.getName()</code> is
     * <code>"Larry Fine"</code>, then the JSONObject will contain
     * <code>"name": "Larry Fine"</code>.
     * 
     * @param bean
     *            An object that has getter methods that should be used to make
     *            a JSONObject.
     */
    public JSONObject(Object bean)
    {
        this();
        this.populateMap(bean);
    }

    /**
     * Construct a JSONObject from an Object, using reflection to find the
     * public members. The resulting JSONObject's keys will be the strings from
     * the names array, and the values will be the field values associated with
     * those keys in the object. If a key is not found or not visible, then it
     * will not be copied into the new JSONObject.
     * 
     * @param object
     *            An object that has fields that should be used to make a
     *            JSONObject.
     * @param names
     *            An array of strings, the names of the fields to be obtained
     *            from the object.
     */
    public JSONObject(Object object, String names[])
    {
        this();
        Class c = object.getClass();
        for (int i = 0; i < names.length; i += 1)
        {
            String name = names[i];
            try
            {
                this.putOpt(name, c.getField(name).get(object));
            }
            catch (Exception ignore)
            {
            }
        }
    }

    /**
     * Construct a JSONObject from a source JSON text string. This is the most
     * commonly used JSONObject constructor.
     * 
     * @param source
     *            A string beginning with <code>{</code>&nbsp;<small>(left
     *            brace)</small> and ending with <code>}</code>
     *            &nbsp;<small>(right brace)</small>.
     * @exception JSONException
     *                If there is a syntax error in the source string or a
     *                duplicated key.
     */
    public JSONObject(String source) throws JSONException
    {
        this(new JSONTokener(source));
    }

    /**
     * Construct a JSONObject from a ResourceBundle.
     * 
     * @param baseName
     *            The ResourceBundle base name.
     * @param locale
     *            The Locale to load the ResourceBundle for.
     * @throws JSONException
     *             If any JSONExceptions are detected.
     */
    public JSONObject(String baseName, Locale locale) throws JSONException
    {
        this();
        ResourceBundle bundle = ResourceBundle.getBundle(baseName, locale,
                Thread.currentThread().getContextClassLoader());

        // Iterate through the keys in the bundle.

        Enumeration keys = bundle.getKeys();
        while (keys.hasMoreElements())
        {
            Object key = keys.nextElement();
            if(key instanceof String)
            {

                // Go through the path, ensuring that there is a nested
                // JSONObject for each
                // segment except the last. Add the value using the last
                // segment's name into
                // the deepest nested JSONObject.

                String[] path = ((String) key).split("\\.");
                int last = path.length - 1;
                JSONObject target = this;
                for (int i = 0; i < last; i += 1)
                {
                    String segment = path[i];
                    JSONObject nextTarget = target.optJSONObject(segment);
                    if(nextTarget == null)
                    {
                        nextTarget = new JSONObject();
                        target.put(segment, nextTarget);
                    }
                    target = nextTarget;
                }
                target.put(path[last], bundle.getString((String) key));
            }
        }
    }

    /**
     * Accumulate values under a key. It is similar to the put method except
     * that if there is already an object stored under the key then a JSONArray
     * is stored under the key to hold all of the accumulated values. If there
     * is already a JSONArray, then the new value is appended to it. In
     * contrast, the put method replaces the previous value.
     * 
     * If only one value is accumulated that is not a JSONArray, then the result
     * will be the same as using put. But if multiple values are accumulated,
     * then the result will be like append.
     * 
     * @param key
     *            A key string.
     * @param value
     *            An object to be accumulated under the key.
     * @return this.
     * @throws JSONException
     *             If the value is an invalid number or if the key is null.
     */
    public JSONObject accumulate(String key, Object value) throws JSONException
    {
        testValidity(value);
        Object object = this.opt(key);
        if(object == null)
        {
            this.put(key,
                    value instanceof JSONArray ? new JSONArray().put(value)
                            : value);
        }
        else if(object instanceof JSONArray)
        {
            ((JSONArray) object).put(value);
        }
        else
        {
            this.put(key, new JSONArray().put(object).put(value));
        }
        return this;
    }

    /**
     * Append values to the array under a key. If the key does not exist in the
     * JSONObject, then the key is put in the JSONObject with its value being a
     * JSONArray containing the value parameter. If the key was already
     * associated with a JSONArray, then the value parameter is appended to it.
     * 
     * @param key
     *            A key string.
     * @param value
     *            An object to be accumulated under the key.
     * @return this.
     * @throws JSONException
     *             If the key is null or if the current value associated with
     *             the key is not a JSONArray.
     */
    public JSONObject append(String key, Object value) throws JSONException
    {
        testValidity(value);
        Object object = this.opt(key);
        if(object == null)
        {
            this.put(key, new JSONArray().put(value));
        }
        else if(object instanceof JSONArray)
        {
            this.put(key, ((JSONArray) object).put(value));
        }
        else
        {
            throw new JSONException("JSONObject[" + key
                    + "] is not a JSONArray.");
        }
        return this;
    }

    /**
     * Produce a string from a double. The string "null" will be returned if the
     * number is not finite.
     * 
     * @param d
     *            A double.
     * @return A String.
     */
    public static String doubleToString(double d)
    {
        if(Double.isInfinite(d) || Double.isNaN(d))
        {
            return "null";
        }

        // Shave off trailing zeros and decimal point, if possible.

        String string = Double.toString(d);
        if(string.indexOf('.') > 0 && string.indexOf('e') < 0
                && string.indexOf('E') < 0)
        {
            while (string.endsWith("0"))
            {
                string = string.substring(0, string.length() - 1);
            }
            if(string.endsWith("."))
            {
                string = string.substring(0, string.length() - 1);
            }
        }
        return string;
    }

    /**
     * Get the value object associated with a key.
     * 
     * @param key
     *            A key string.
     * @return The object associated with the key.
     * @throws JSONException
     *             if the key is not found.
     */
    public Object get(String key) throws JSONException
    {
        if(key == null)
        {
            throw new JSONException("Null key.");
        }
        Object object = this.opt(key);
        if(object == null)
        {
            throw new JSONException("JSONObject[" + quote(key) + "] not found.");
        }
        return object;
    }

    /**
     * Get the boolean value associated with a key.
     * 
     * @param key
     *            A key string.
     * @return The truth.
     * @throws JSONException
     *             if the value is not a Boolean or the String "true" or
     *             "false".
     */
    public boolean getBoolean(String key) throws JSONException
    {
        Object object = this.get(key);
        if(object.equals(Boolean.FALSE)
                || (object instanceof String && ((String) object)
                        .equalsIgnoreCase("false")))
        {
            return false;
        }
        else if(object.equals(Boolean.TRUE)
                || (object instanceof String && ((String) object)
                        .equalsIgnoreCase("true")))
        {
            return true;
        }
        throw new JSONException("JSONObject[" + quote(key)
                + "] is not a Boolean.");
    }

    /**
     * Get the double value associated with a key.
     * 
     * @param key
     *            A key string.
     * @return The numeric value.
     * @throws JSONException
     *             if the key is not found or if the value is not a Number
     *             object and cannot be converted to a number.
     */
    public double getDouble(String key) throws JSONException
    {
        Object object = this.get(key);
        try
        {
            return object instanceof Number ? ((Number) object).doubleValue()
                    : Double.parseDouble((String) object);
        }
        catch (Exception e)
        {
            throw new JSONException("JSONObject[" + quote(key)
                    + "] is not a number.");
        }
    }

    /**
     * Get the int value associated with a key.
     * 
     * @param key
     *            A key string.
     * @return The integer value.
     * @throws JSONException
     *             if the key is not found or if the value cannot be converted
     *             to an integer.
     */
    public int getInt(String key) throws JSONException
    {
        Object object = this.get(key);
        try
        {
            return object instanceof Number ? ((Number) object).intValue()
                    : Integer.parseInt((String) object);
        }
        catch (Exception e)
        {
            throw new JSONException("JSONObject[" + quote(key)
                    + "] is not an int.");
        }
    }

    /**
     * Get the JSONArray value associated with a key.
     * 
     * @param key
     *            A key string.
     * @return A JSONArray which is the value.
     * @throws JSONException
     *             if the key is not found or if the value is not a JSONArray.
     */
    public JSONArray getJSONArray(String key) throws JSONException
    {
        Object object = this.get(key);
        if(object instanceof JSONArray)
        {
            return (JSONArray) object;
        }
        throw new JSONException("JSONObject[" + quote(key)
                + "] is not a JSONArray.");
    }

    /**
     * Get the JSONObject value associated with a key.
     * 
     * @param key
     *            A key string.
     * @return A JSONObject which is the value.
     * @throws JSONException
     *             if the key is not found or if the value is not a JSONObject.
     */
    public JSONObject getJSONObject(String key) throws JSONException
    {
        Object object = this.get(key);
        if(object instanceof JSONObject)
        {
            return (JSONObject) object;
        }
        throw new JSONException("JSONObject[" + quote(key)
                + "] is not a JSONObject.");
    }

    /**
     * Get the long value associated with a key.
     * 
     * @param key
     *            A key string.
     * @return The long value.
     * @throws JSONException
     *             if the key is not found or if the value cannot be converted
     *             to a long.
     */
    public long getLong(String key) throws JSONException
    {
        Object object = this.get(key);
        try
        {
            return object instanceof Number ? ((Number) object).longValue()
                    : Long.parseLong((String) object);
        }
        catch (Exception e)
        {
            throw new JSONException("JSONObject[" + quote(key)
                    + "] is not a long.");
        }
    }

    /**
     * Get an array of field names from a JSONObject.
     * 
     * @return An array of field names, or null if there are no names.
     */
    public static String[] getNames(JSONObject jo)
    {
        int length = jo.length();
        if(length == 0)
        {
            return null;
        }
        Iterator iterator = jo.keys();
        String[] names = new String[length];
        int i = 0;
        while (iterator.hasNext())
        {
            names[i] = (String) iterator.next();
            i += 1;
        }
        return names;
    }

    /**
     * Get an array of field names from an Object.
     * 
     * @return An array of field names, or null if there are no names.
     */
    public static String[] getNames(Object object)
    {
        if(object == null)
        {
            return null;
        }
        Class klass = object.getClass();
        Field[] fields = klass.getFields();
        int length = fields.length;
        if(length == 0)
        {
            return null;
        }
        String[] names = new String[length];
        for (int i = 0; i < length; i += 1)
        {
            names[i] = fields[i].getName();
        }
        return names;
    }

    /**
     * Get the string associated with a key.
     * 
     * @param key
     *            A key string.
     * @return A string which is the value.
     * @throws JSONException
     *             if there is no string value for the key.
     */
    public String getString(String key) throws JSONException
    {
        Object object = this.get(key);
        if(object instanceof String)
        {
            return (String) object;
        }
        throw new JSONException("JSONObject[" + quote(key) + "] not a string.");
    }

    /**
     * Determine if the JSONObject contains a specific key.
     * 
     * @param key
     *            A key string.
     * @return true if the key exists in the JSONObject.
     */
    public boolean has(String key)
    {
        return this.map.containsKey(key);
    }

    /**
     * Increment a property of a JSONObject. If there is no such property,
     * create one with a value of 1. If there is such a property, and if it is
     * an Integer, Long, Double, or Float, then add one to it.
     * 
     * @param key
     *            A key string.
     * @return this.
     * @throws JSONException
     *             If there is already a property with this name that is not an
     *             Integer, Long, Double, or Float.
     */
    public JSONObject increment(String key) throws JSONException
    {
        Object value = this.opt(key);
        if(value == null)
        {
            this.put(key, 1);
        }
        else if(value instanceof Integer)
        {
            this.put(key, ((Integer) value).intValue() + 1);
        }
        else if(value instanceof Long)
        {
            this.put(key, ((Long) value).longValue() + 1);
        }
        else if(value instanceof Double)
        {
            this.put(key, ((Double) value).doubleValue() + 1);
        }
        else if(value instanceof Float)
        {
            this.put(key, ((Float) value).floatValue() + 1);
        }
        else
        {
            throw new JSONException("Unable to increment [" + quote(key) + "].");
        }
        return this;
    }

    /**
     * Determine if the value associated with the key is null or if there is no
     * value.
     * 
     * @param key
     *            A key string.
     * @return true if there is no value associated with the key or if the value
     *         is the JSONObject.NULL object.
     */
    public boolean isNull(String key)
    {
        return JSONObject.NULL.equals(this.opt(key));
    }

    /**
     * Get an enumeration of the keys of the JSONObject.
     * 
     * @return An iterator of the keys.
     */
    public Iterator keys()
    {
        return this.keySet().iterator();
    }

    /**
     * Get a set of keys of the JSONObject.
     * 
     * @return A keySet.
     */
    public Set keySet()
    {
        return this.map.keySet();
    }

    /**
     * Get the number of keys stored in the JSONObject.
     * 
     * @return The number of keys in the JSONObject.
     */
    public int length()
    {
        return this.map.size();
    }

    /**
     * Produce a JSONArray containing the names of the elements of this
     * JSONObject.
     * 
     * @return A JSONArray containing the key strings, or null if the JSONObject
     *         is empty.
     */
    public JSONArray names()
    {
        JSONArray ja = new JSONArray();
        Iterator keys = this.keys();
        while (keys.hasNext())
        {
            ja.put(keys.next());
        }
        return ja.length() == 0 ? null : ja;
    }

    /**
     * Produce a string from a Number.
     * 
     * @param number
     *            A Number
     * @return A String.
     * @throws JSONException
     *             If n is a non-finite number.
     */
    public static String numberToString(Number number) throws JSONException
    {
        if(number == null)
        {
            throw new JSONException("Null pointer");
        }
        testValidity(number);

        // Shave off trailing zeros and decimal point, if possible.

        String string = number.toString();
        if(string.indexOf('.') > 0 && string.indexOf('e') < 0
                && string.indexOf('E') < 0)
        {
            while (string.endsWith("0"))
            {
                string = string.substring(0, string.length() - 1);
            }
            if(string.endsWith("."))
            {
                string = string.substring(0, string.length() - 1);
            }
        }
        return string;
    }

    /**
     * Get an optional value associated with a key.
     * 
     * @param key
     *            A key string.
     * @return An object which is the value, or null if there is no value.
     */
    public Object opt(String key)
    {
        return key == null ? null : this.map.get(key);
    }

    /**
     * Get an optional boolean associated with a key. It returns false if there
     * is no such key, or if the value is not Boolean.TRUE or the String "true".
     * 
     * @param key
     *            A key string.
     * @return The truth.
     */
    public boolean optBoolean(String key)
    {
        return this.optBoolean(key, false);
    }

    /**
     * Get an optional boolean associated with a key. It returns the
     * defaultValue if there is no such key, or if it is not a Boolean or the
     * String "true" or "false" (case insensitive).
     * 
     * @param key
     *            A key string.
     * @param defaultValue
     *            The default.
     * @return The truth.
     */
    public boolean optBoolean(String key, boolean defaultValue)
    {
        try
        {
            return this.getBoolean(key);
        }
        catch (Exception e)
        {
            return defaultValue;
        }
    }

    /**
     * Get an optional double associated with a key, or NaN if there is no such
     * key or if its value is not a number. If the value is a string, an attempt
     * will be made to evaluate it as a number.
     * 
     * @param key
     *            A string which is the key.
     * @return An object which is the value.
     */
    public double optDouble(String key)
    {
        return this.optDouble(key, Double.NaN);
    }

    /**
     * Get an optional double associated with a key, or the defaultValue if
     * there is no such key or if its value is not a number. If the value is a
     * string, an attempt will be made to evaluate it as a number.
     * 
     * @param key
     *            A key string.
     * @param defaultValue
     *            The default.
     * @return An object which is the value.
     */
    public double optDouble(String key, double defaultValue)
    {
        try
        {
            return this.getDouble(key);
        }
        catch (Exception e)
        {
            return defaultValue;
        }
    }

    /**
     * Get an optional int value associated with a key, or zero if there is no
     * such key or if the value is not a number. If the value is a string, an
     * attempt will be made to evaluate it as a number.
     * 
     * @param key
     *            A key string.
     * @return An object which is the value.
     */
    public int optInt(String key)
    {
        return this.optInt(key, 0);
    }

    /**
     * Get an optional int value associated with a key, or the default if there
     * is no such key or if the value is not a number. If the value is a string,
     * an attempt will be made to evaluate it as a number.
     * 
     * @param key
     *            A key string.
     * @param defaultValue
     *            The default.
     * @return An object which is the value.
     */
    public int optInt(String key, int defaultValue)
    {
        try
        {
            return this.getInt(key);
        }
        catch (Exception e)
        {
            return defaultValue;
        }
    }

    /**
     * Get an optional JSONArray associated with a key. It returns null if there
     * is no such key, or if its value is not a JSONArray.
     * 
     * @param key
     *            A key string.
     * @return A JSONArray which is the value.
     */
    public JSONArray optJSONArray(String key)
    {
        Object o = this.opt(key);
        return o instanceof JSONArray ? (JSONArray) o : null;
    }

    /**
     * Get an optional JSONObject associated with a key. It returns null if
     * there is no such key, or if its value is not a JSONObject.
     * 
     * @param key
     *            A key string.
     * @return A JSONObject which is the value.
     */
    public JSONObject optJSONObject(String key)
    {
        Object object = this.opt(key);
        return object instanceof JSONObject ? (JSONObject) object : null;
    }

    /**
     * Get an optional long value associated with a key, or zero if there is no
     * such key or if the value is not a number. If the value is a string, an
     * attempt will be made to evaluate it as a number.
     * 
     * @param key
     *            A key string.
     * @return An object which is the value.
     */
    public long optLong(String key)
    {
        return this.optLong(key, 0);
    }

    /**
     * Get an optional long value associated with a key, or the default if there
     * is no such key or if the value is not a number. If the value is a string,
     * an attempt will be made to evaluate it as a number.
     * 
     * @param key
     *            A key string.
     * @param defaultValue
     *            The default.
     * @return An object which is the value.
     */
    public long optLong(String key, long defaultValue)
    {
        try
        {
            return this.getLong(key);
        }
        catch (Exception e)
        {
            return defaultValue;
        }
    }

    /**
     * Get an optional string associated with a key. It returns an empty string
     * if there is no such key. If the value is not a string and is not null,
     * then it is converted to a string.
     * 
     * @param key
     *            A key string.
     * @return A string which is the value.
     */
    public String optString(String key)
    {
        return this.optString(key, "");
    }

    /**
     * Get an optional string associated with a key. It returns the defaultValue
     * if there is no such key.
     * 
     * @param key
     *            A key string.
     * @param defaultValue
     *            The default.
     * @return A string which is the value.
     */
    public String optString(String key, String defaultValue)
    {
        Object object = this.opt(key);
        return NULL.equals(object) ? defaultValue : object.toString();
    }

    private void populateMap(Object bean)
    {
        Class klass = bean.getClass();

        // If klass is a System class then set includeSuperClass to false.

        boolean includeSuperClass = klass.getClassLoader() != null;

        Method[] methods = includeSuperClass ? klass.getMethods() : klass
                .getDeclaredMethods();
        for (int i = 0; i < methods.length; i += 1)
        {
            try
            {
                Method method = methods[i];
                if(Modifier.isPublic(method.getModifiers()))
                {
                    String name = method.getName();
                    String key = "";
                    if(name.startsWith("get"))
                    {
                        if("getClass".equals(name)
                                || "getDeclaringClass".equals(name))
                        {
                            key = "";
                        }
                        else
                        {
                            key = name.substring(3);
                        }
                    }
                    else if(name.startsWith("is"))
                    {
                        key = name.substring(2);
                    }
                    if(key.length() > 0 && Character.isUpperCase(key.charAt(0))
                            && method.getParameterTypes().length == 0)
                    {
                        if(key.length() == 1)
                        {
                            key = key.toLowerCase();
                        }
                        else if(!Character.isUpperCase(key.charAt(1)))
                        {
                            key = key.substring(0, 1).toLowerCase()
                                    + key.substring(1);
                        }

                        Object result = method.invoke(bean, (Object[]) null);
                        if(result != null)
                        {
                            this.map.put(key, wrap(result));
                        }
                    }
                }
            }
            catch (Exception ignore)
            {
            }
        }
    }

    /**
     * Put a key/boolean pair in the JSONObject.
     * 
     * @param key
     *            A key string.
     * @param value
     *            A boolean which is the value.
     * @return this.
     * @throws JSONException
     *             If the key is null.
     */
    public JSONObject put(String key, boolean value) throws JSONException
    {
        this.put(key, value ? Boolean.TRUE : Boolean.FALSE);
        return this;
    }

    /**
     * Put a key/value pair in the JSONObject, where the value will be a
     * JSONArray which is produced from a Collection.
     * 
     * @param key
     *            A key string.
     * @param value
     *            A Collection value.
     * @return this.
     * @throws JSONException
     */
    public JSONObject put(String key, Collection value) throws JSONException
    {
        this.put(key, new JSONArray(value));
        return this;
    }

    /**
     * Put a key/double pair in the JSONObject.
     * 
     * @param key
     *            A key string.
     * @param value
     *            A double which is the value.
     * @return this.
     * @throws JSONException
     *             If the key is null or if the number is invalid.
     */
    public JSONObject put(String key, double value) throws JSONException
    {
        this.put(key, new Double(value));
        return this;
    }

    /**
     * Put a key/int pair in the JSONObject.
     * 
     * @param key
     *            A key string.
     * @param value
     *            An int which is the value.
     * @return this.
     * @throws JSONException
     *             If the key is null.
     */
    public JSONObject put(String key, int value) throws JSONException
    {
        this.put(key, new Integer(value));
        return this;
    }

    /**
     * Put a key/long pair in the JSONObject.
     * 
     * @param key
     *            A key string.
     * @param value
     *            A long which is the value.
     * @return this.
     * @throws JSONException
     *             If the key is null.
     */
    public JSONObject put(String key, long value) throws JSONException
    {
        this.put(key, new Long(value));
        return this;
    }

    /**
     * Put a key/value pair in the JSONObject, where the value will be a
     * JSONObject which is produced from a Map.
     * 
     * @param key
     *            A key string.
     * @param value
     *            A Map value.
     * @return this.
     * @throws JSONException
     */
    public JSONObject put(String key, Map value) throws JSONException
    {
        this.put(key, new JSONObject(value));
        return this;
    }

    /**
     * Put a key/value pair in the JSONObject. If the value is null, then the
     * key will be removed from the JSONObject if it is present.
     * 
     * @param key
     *            A key string.
     * @param value
     *            An object which is the value. It should be of one of these
     *            types: Boolean, Double, Integer, JSONArray, JSONObject, Long,
     *            String, or the JSONObject.NULL object.
     * @return this.
     * @throws JSONException
     *             If the value is non-finite number or if the key is null.
     */
    public JSONObject put(String key, Object value) throws JSONException
    {
        if(key == null)
        {
            throw new NullPointerException("Null key.");
        }
        if(value != null)
        {
            testValidity(value);
            this.map.put(key, value);
        }
        else
        {
            this.remove(key);
        }
        return this;
    }

    /**
     * Put a key/value pair in the JSONObject, but only if the key and the value
     * are both non-null, and only if there is not already a member with that
     * name.
     * 
     * @param key
     * @param value
     * @return his.
     * @throws JSONException
     *             if the key is a duplicate
     */
    public JSONObject putOnce(String key, Object value) throws JSONException
    {
        if(key != null && value != null)
        {
            if(this.opt(key) != null)
            {
                throw new JSONException("Duplicate key \"" + key + "\"");
            }
            this.put(key, value);
        }
        return this;
    }

    /**
     * Put a key/value pair in the JSONObject, but only if the key and the value
     * are both non-null.
     * 
     * @param key
     *            A key string.
     * @param value
     *            An object which is the value. It should be of one of these
     *            types: Boolean, Double, Integer, JSONArray, JSONObject, Long,
     *            String, or the JSONObject.NULL object.
     * @return this.
     * @throws JSONException
     *             If the value is a non-finite number.
     */
    public JSONObject putOpt(String key, Object value) throws JSONException
    {
        if(key != null && value != null)
        {
            this.put(key, value);
        }
        return this;
    }

    /**
     * Produce a string in double quotes with backslash sequences in all the
     * right places. A backslash will be inserted within </, producing <\/,
     * allowing JSON text to be delivered in HTML. In JSON text, a string cannot
     * contain a control character or an unescaped quote or backslash.
     * 
     * @param string
     *            A String
     * @return A String correctly formatted for insertion in a JSON text.
     */
    public static String quote(String string)
    {
        StringWriter sw = new StringWriter();
        synchronized (sw.getBuffer())
        {
            try
            {
                return quote(string, sw).toString();
            }
            catch (IOException ignored)
            {
                // will never happen - we are writing to a string writer
                return "";
            }
        }
    }

    public static Writer quote(String string, Writer w) throws IOException
    {
        if(string == null || string.length() == 0)
        {
            w.write("\"\"");
            return w;
        }

        char b;
        char c = 0;
        String hhhh;
        int i;
        int len = string.length();

        w.write('"');
        for (i = 0; i < len; i += 1)
        {
            b = c;
            c = string.charAt(i);
            switch (c)
            {
                case '\\':
                case '"':
                    w.write('\\');
                    w.write(c);
                    break;
                case '/':
                    if(b == '<')
                    {
                        w.write('\\');
                    }
                    w.write(c);
                    break;
                case '\b':
                    w.write("\\b");
                    break;
                case '\t':
                    w.write("\\t");
                    break;
                case '\n':
                    w.write("\\n");
                    break;
                case '\f':
                    w.write("\\f");
                    break;
                case '\r':
                    w.write("\\r");
                    break;
                default:
                    if(c < ' ' || (c >= '\u0080' && c < '\u00a0')
                            || (c >= '\u2000' && c < '\u2100'))
                    {
                        w.write("\\u");
                        hhhh = Integer.toHexString(c);
                        w.write("0000", 0, 4 - hhhh.length());
                        w.write(hhhh);
                    }
                    else
                    {
                        w.write(c);
                    }
            }
        }
        w.write('"');
        return w;
    }

    /**
     * Remove a name and its value, if present.
     * 
     * @param key
     *            The name to be removed.
     * @return The value that was associated with the name, or null if there was
     *         no value.
     */
    public Object remove(String key)
    {
        return this.map.remove(key);
    }

    /**
     * Try to convert a string into a number, boolean, or null. If the string
     * can't be converted, return the string.
     * 
     * @param string
     *            A String.
     * @return A simple JSON value.
     */
    public static Object stringToValue(String string)
    {
        Double d;
        if(string.equals(""))
        {
            return string;
        }
        if(string.equalsIgnoreCase("true"))
        {
            return Boolean.TRUE;
        }
        if(string.equalsIgnoreCase("false"))
        {
            return Boolean.FALSE;
        }
        if(string.equalsIgnoreCase("null"))
        {
            return JSONObject.NULL;
        }

        /*
         * If it might be a number, try converting it. If a number cannot be
         * produced, then the value will just be a string.
         */

        char b = string.charAt(0);
        if((b >= '0' && b <= '9') || b == '-')
        {
            try
            {
                if(string.indexOf('.') > -1 || string.indexOf('e') > -1
                        || string.indexOf('E') > -1)
                {
                    d = Double.valueOf(string);
                    if(!d.isInfinite() && !d.isNaN())
                    {
                        return d;
                    }
                }
                else
                {
                    Long myLong = new Long(string);
                    if(string.equals(myLong.toString()))
                    {
                        if(myLong.longValue() == myLong.intValue())
                        {
                            return new Integer(myLong.intValue());
                        }
                        else
                        {
                            return myLong;
                        }
                    }
                }
            }
            catch (Exception ignore)
            {
            }
        }
        return string;
    }

    /**
     * Throw an exception if the object is a NaN or infinite number.
     * 
     * @param o
     *            The object to test.
     * @throws JSONException
     *             If o is a non-finite number.
     */
    public static void testValidity(Object o) throws JSONException
    {
        if(o != null)
        {
            if(o instanceof Double)
            {
                if(((Double) o).isInfinite() || ((Double) o).isNaN())
                {
                    throw new JSONException(
                            "JSON does not allow non-finite numbers.");
                }
            }
            else if(o instanceof Float)
            {
                if(((Float) o).isInfinite() || ((Float) o).isNaN())
                {
                    throw new JSONException(
                            "JSON does not allow non-finite numbers.");
                }
            }
        }
    }

    /**
     * Produce a JSONArray containing the values of the members of this
     * JSONObject.
     * 
     * @param names
     *            A JSONArray containing a list of key strings. This determines
     *            the sequence of the values in the result.
     * @return A JSONArray of values.
     * @throws JSONException
     *             If any of the values are non-finite numbers.
     */
    public JSONArray toJSONArray(JSONArray names) throws JSONException
    {
        if(names == null || names.length() == 0)
        {
            return null;
        }
        JSONArray ja = new JSONArray();
        for (int i = 0; i < names.length(); i += 1)
        {
            ja.put(this.opt(names.getString(i)));
        }
        return ja;
    }

    /**
     * Make a JSON text of this JSONObject. For compactness, no whitespace is
     * added. If this would not result in a syntactically correct JSON text,
     * then null will be returned instead.
     * <p>
     * Warning: This method assumes that the data structure is acyclical.
     * 
     * @return a printable, displayable, portable, transmittable representation
     *         of the object, beginning with <code>{</code>&nbsp;<small>(left
     *         brace)</small> and ending with <code>}</code>&nbsp;<small>(right
     *         brace)</small>.
     */
    public String toString()
    {
        try
        {
            return this.toString(0);
        }
        catch (Exception e)
        {
            return null;
        }
    }

    /**
     * Make a prettyprinted JSON text of this JSONObject.
     * <p>
     * Warning: This method assumes that the data structure is acyclical.
     * 
     * @param indentFactor
     *            The number of spaces to add to each level of indentation.
     * @return a printable, displayable, portable, transmittable representation
     *         of the object, beginning with <code>{</code>&nbsp;<small>(left
     *         brace)</small> and ending with <code>}</code>&nbsp;<small>(right
     *         brace)</small>.
     * @throws JSONException
     *             If the object contains an invalid number.
     */
    public String toString(int indentFactor) throws JSONException
    {
        StringWriter w = new StringWriter();
        synchronized (w.getBuffer())
        {
            return this.write(w, indentFactor, 0).toString();
        }
    }

    /**
     * Make a JSON text of an Object value. If the object has an
     * value.toJSONString() method, then that method will be used to produce the
     * JSON text. The method is required to produce a strictly conforming text.
     * If the object does not contain a toJSONString method (which is the most
     * common case), then a text will be produced by other means. If the value
     * is an array or Collection, then a JSONArray will be made from it and its
     * toJSONString method will be called. If the value is a MAP, then a
     * JSONObject will be made from it and its toJSONString method will be
     * called. Otherwise, the value's toString method will be called, and the
     * result will be quoted.
     * 
     * <p>
     * Warning: This method assumes that the data structure is acyclical.
     * 
     * @param value
     *            The value to be serialized.
     * @return a printable, displayable, transmittable representation of the
     *         object, beginning with <code>{</code>&nbsp;<small>(left
     *         brace)</small> and ending with <code>}</code>&nbsp;<small>(right
     *         brace)</small>.
     * @throws JSONException
     *             If the value is or contains an invalid number.
     */
    public static String valueToString(Object value) throws JSONException
    {
        if(value == null || value.equals(null))
        {
            return "null";
        }
        if(value instanceof JSONString)
        {
            Object object;
            try
            {
                object = ((JSONString) value).toJSONString();
            }
            catch (Exception e)
            {
                throw new JSONException(e);
            }
            if(object instanceof String)
            {
                return (String) object;
            }
            throw new JSONException("Bad value from toJSONString: " + object);
        }
        if(value instanceof Number)
        {
            return numberToString((Number) value);
        }
        if(value instanceof Boolean || value instanceof JSONObject
                || value instanceof JSONArray)
        {
            return value.toString();
        }
        if(value instanceof Map)
        {
            return new JSONObject((Map) value).toString();
        }
        if(value instanceof Collection)
        {
            return new JSONArray((Collection) value).toString();
        }
        if(value.getClass().isArray())
        {
            return new JSONArray(value).toString();
        }
        return quote(value.toString());
    }

    /**
     * Wrap an object, if necessary. If the object is null, return the NULL
     * object. If it is an array or collection, wrap it in a JSONArray. If it is
     * a map, wrap it in a JSONObject. If it is a standard property (Double,
     * String, et al) then it is already wrapped. Otherwise, if it comes from
     * one of the java packages, turn it into a string. And if it doesn't, try
     * to wrap it in a JSONObject. If the wrapping fails, then null is returned.
     * 
     * @param object
     *            The object to wrap
     * @return The wrapped value
     */
    public static Object wrap(Object object)
    {
        try
        {
            if(object == null)
            {
                return NULL;
            }
            if(object instanceof JSONObject || object instanceof JSONArray
                    || NULL.equals(object) || object instanceof JSONString
                    || object instanceof Byte || object instanceof Character
                    || object instanceof Short || object instanceof Integer
                    || object instanceof Long || object instanceof Boolean
                    || object instanceof Float || object instanceof Double
                    || object instanceof String)
            {
                return object;
            }

            if(object instanceof Collection)
            {
                return new JSONArray((Collection) object);
            }
            if(object.getClass().isArray())
            {
                return new JSONArray(object);
            }
            if(object instanceof Map)
            {
                return new JSONObject((Map) object);
            }
            Package objectPackage = object.getClass().getPackage();
            String objectPackageName = objectPackage != null ? objectPackage
                    .getName() : "";
            if(objectPackageName.startsWith("java.")
                    || objectPackageName.startsWith("javax.")
                    || object.getClass().getClassLoader() == null)
            {
                return object.toString();
            }
            return new JSONObject(object);
        }
        catch (Exception exception)
        {
            return null;
        }
    }

    /**
     * Write the contents of the JSONObject as JSON text to a writer. For
     * compactness, no whitespace is added.
     * <p>
     * Warning: This method assumes that the data structure is acyclical.
     * 
     * @return The writer.
     * @throws JSONException
     */
    public Writer write(Writer writer) throws JSONException
    {
        return this.write(writer, 0, 0);
    }

    static final Writer writeValue(Writer writer, Object value,
            int indentFactor, int indent) throws JSONException, IOException
    {
        if(value == null || value.equals(null))
        {
            writer.write("null");
        }
        else if(value instanceof JSONObject)
        {
            ((JSONObject) value).write(writer, indentFactor, indent);
        }
        else if(value instanceof JSONArray)
        {
            ((JSONArray) value).write(writer, indentFactor, indent);
        }
        else if(value instanceof Map)
        {
            new JSONObject((Map) value).write(writer, indentFactor, indent);
        }
        else if(value instanceof Collection)
        {
            new JSONArray((Collection) value).write(writer, indentFactor,
                    indent);
        }
        else if(value.getClass().isArray())
        {
            new JSONArray(value).write(writer, indentFactor, indent);
        }
        else if(value instanceof Number)
        {
            writer.write(numberToString((Number) value));
        }
        else if(value instanceof Boolean)
        {
            writer.write(value.toString());
        }
        else if(value instanceof JSONString)
        {
            Object o;
            try
            {
                o = ((JSONString) value).toJSONString();
            }
            catch (Exception e)
            {
                throw new JSONException(e);
            }
            writer.write(o != null ? o.toString() : quote(value.toString()));
        }
        else
        {
            quote(value.toString(), writer);
        }
        return writer;
    }

    static final void indent(Writer writer, int indent) throws IOException
    {
        for (int i = 0; i < indent; i += 1)
        {
            writer.write(' ');
        }
    }

    /**
     * Write the contents of the JSONObject as JSON text to a writer. For
     * compactness, no whitespace is added.
     * <p>
     * Warning: This method assumes that the data structure is acyclical.
     * 
     * @return The writer.
     * @throws JSONException
     */
    Writer write(Writer writer, int indentFactor, int indent)
            throws JSONException
    {
        try
        {
            boolean commanate = false;
            final int length = this.length();
            Iterator keys = this.keys();
            writer.write('{');

            if(length == 1)
            {
                Object key = keys.next();
                writer.write(quote(key.toString()));
                writer.write(':');
                if(indentFactor > 0)
                {
                    writer.write(' ');
                }
                writeValue(writer, this.map.get(key), indentFactor, indent);
            }
            else if(length != 0)
            {
                final int newindent = indent + indentFactor;
                while (keys.hasNext())
                {
                    Object key = keys.next();
                    if(commanate)
                    {
                        writer.write(',');
                    }
                    if(indentFactor > 0)
                    {
                        writer.write('\n');
                    }
                    indent(writer, newindent);
                    writer.write(quote(key.toString()));
                    writer.write(':');
                    if(indentFactor > 0)
                    {
                        writer.write(' ');
                    }
                    writeValue(writer, this.map.get(key), indentFactor,
                            newindent);
                    commanate = true;
                }
                if(indentFactor > 0)
                {
                    writer.write('\n');
                }
                indent(writer, indent);
            }
            writer.write('}');
            return writer;
        }
        catch (IOException exception)
        {
            throw new JSONException(exception);
        }
    }
} public abstract class AbstractPacket {

    /**
     * Encode the packet data into the ByteBuf stream. Complex data sets may need specific data handlers (See @link{cpw.mods.fml.common.network.ByteBuffUtils})
     *
     * @param ctx    channel context
     * @param buffer the buffer to encode into
     */
    public abstract void encodeInto(ChannelHandlerContext ctx, ByteBuf buffer);

    /**
     * Decode the packet data from the ByteBuf stream. Complex data sets may need specific data handlers (See @link{cpw.mods.fml.common.network.ByteBuffUtils})
     *
     * @param ctx    channel context
     * @param buffer the buffer to decode from
     */
    public abstract void decodeInto(ChannelHandlerContext ctx, ByteBuf buffer);

    /**
     * Handle a packet on the client side. Note this occurs after decoding has completed.
     *
     * @param player the player reference
     */
    public abstract void handleClientSide(EntityPlayer player);

    /**
     * Handle a packet on the server side. Note this occurs after decoding has completed.
     *
     * @param player the player reference
     */
    public abstract void handleServerSide(EntityPlayer player);
} @ChannelHandler.Sharable
public class PacketPipeline extends MessageToMessageCodec<FMLProxyPacket, AbstractPacket> {

    private EnumMap<Side, FMLEmbeddedChannel>           channels;
    private LinkedList<Class<? extends AbstractPacket>> packets           = new LinkedList<Class<? extends AbstractPacket>>();
    private boolean                                     isPostInitialised = false;

    public PacketPipeline(String packetsPackage, Class<?> clazz)
    {
//        try
//        {
//            ImmutableSet<ClassInfo> set = ClassPath.from(ClassLoader.getSystemClassLoader()).getTopLevelClassesRecursive(packetsPackage);
//            Iterator<ClassInfo> it = set.iterator();
//            while(it.hasNext())
//            {
//                ClassInfo info = it.next();
//                Class<?> c = Class.forName(info.getName());
//                if(Reflect.isInstanceof(c, clazz))
//                {
//                    registerPacket((Class<? extends AbstractPacket>) c);
//                }
//            }
//        }
//        catch (IOException e)
//        {
//            e.printStackTrace();
//        }
//        catch (ClassNotFoundException e)
//        {
//            e.printStackTrace();
//        }
        this.registerPacket(PacketAIEStackUpdate.class);
        this.registerPacket(PacketGetAI.class);
        this.registerPacket(PacketUpdateAI.class);
        this.registerPacket(PacketUpdateAIEmitter.class);
    }
    
    /**
     * Register your packet with the pipeline. Discriminators are automatically set.
     *
     * @param clazz the class to register
     *
     * @return whether registration was successful. Failure may occur if 256 packets have been registered or if the registry already contains this packet
     */
    public boolean registerPacket(Class<? extends AbstractPacket> clazz) {
        if (this.packets.size() > 256) {
            // You should log here!!
            return false;
        }

        if (this.packets.contains(clazz)) {
            // You should log here!!
            return false;
        }

        if (this.isPostInitialised) {
            // You should log here!!
            return false;
        }

        this.packets.add(clazz);
        return true;
    }

    // In line encoding of the packet, including discriminator setting
    @Override
    protected void encode(ChannelHandlerContext ctx, AbstractPacket msg, List<Object> out) throws Exception {
        ByteBuf buffer = Unpooled.buffer();
        Class<? extends AbstractPacket> clazz = msg.getClass();
        if (!this.packets.contains(msg.getClass())) {
            throw new NullPointerException("No Packet Registered for: " + msg.getClass().getCanonicalName());
        }

        byte discriminator = (byte) this.packets.indexOf(clazz);
        buffer.writeByte(discriminator);
        msg.encodeInto(ctx, buffer);
        FMLProxyPacket proxyPacket = new FMLProxyPacket(buffer.copy(), ctx.channel().attr(NetworkRegistry.FML_CHANNEL).get());
        out.add(proxyPacket);
    }

    // In line decoding and handling of the packet
    @Override
    protected void decode(ChannelHandlerContext ctx, FMLProxyPacket msg, List<Object> out) throws Exception {
        ByteBuf payload = msg.payload();
        byte discriminator = payload.readByte();
        Class<? extends AbstractPacket> clazz = this.packets.get(discriminator);
        if (clazz == null) {
            throw new NullPointerException("No packet registered for discriminator: " + discriminator);
        }

        AbstractPacket pkt = clazz.newInstance();
        pkt.decodeInto(ctx, payload.slice());

        EntityPlayer player;
        switch (FMLCommonHandler.instance().getEffectiveSide()) {
            case CLIENT:
                player = this.getClientPlayer();
                pkt.handleClientSide(player);
                break;

            case SERVER:
                INetHandler netHandler = ctx.channel().attr(NetworkRegistry.NET_HANDLER).get();
                player = ((NetHandlerPlayServer) netHandler).playerEntity;
                pkt.handleServerSide(player);
                break;

            default:
        }

        out.add(pkt);
    }

    /** Method to call from FMLInitializationEvent */
    public void initialise() 
    {
        this.channels = NetworkRegistry.INSTANCE.newChannel("CustomAINettyChannel", this);
    }

    /** Method to call from FMLPostInitializationEvent
     Ensures that packet discriminators are common between server and client by using logical sorting
     */
    public void postInitialise() {
        if (this.isPostInitialised) {
            return;
        }

        this.isPostInitialised = true;
        Collections.sort(this.packets, new Comparator<Class<? extends AbstractPacket>>() {

            @Override
            public int compare(Class<? extends AbstractPacket> clazz1, Class<? extends AbstractPacket> clazz2) {
                int com = String.CASE_INSENSITIVE_ORDER.compare(clazz1.getCanonicalName(), clazz2.getCanonicalName());
                if (com == 0) {
                    com = clazz1.getCanonicalName().compareTo(clazz2.getCanonicalName());
                }

                return com;
            }
        });
    }

    @SideOnly(Side.CLIENT)
    private EntityPlayer getClientPlayer() {
        return Minecraft.getMinecraft().thePlayer;
    }

    /**
     * Send this message to everyone.
     * <p/>
     * Adapted from CPW's code in cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper
     *
     * @param message The message to send
     */
    public void sendToAll(AbstractPacket message) {
        this.channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGET).set(FMLOutboundHandler.OutboundTarget.ALL);
        this.channels.get(Side.SERVER).writeAndFlush(message);
    }

    /**
     * Send this message to the specified player.
     * <p/>
     * Adapted from CPW's code in cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper
     *
     * @param message The message to send
     * @param player  The player to send it to
     */
    public void sendTo(AbstractPacket message, EntityPlayerMP player) {
        this.channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGET).set(FMLOutboundHandler.OutboundTarget.PLAYER);
        this.channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGETARGS).set(player);
        this.channels.get(Side.SERVER).writeAndFlush(message);
    }

    /**
     * Send this message to everyone within a certain range of a point.
     * <p/>
     * Adapted from CPW's code in cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper
     *
     * @param message The message to send
     * @param point   The {@link cpw.mods.fml.common.network.NetworkRegistry.TargetPoint} around which to send
     */
    public void sendToAllAround(AbstractPacket message, NetworkRegistry.TargetPoint point) {
        this.channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGET).set(FMLOutboundHandler.OutboundTarget.ALLAROUNDPOINT);
        this.channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGETARGS).set(point);
        this.channels.get(Side.SERVER).writeAndFlush(message);
    }

    /**
     * Send this message to everyone within the supplied dimension.
     * <p/>
     * Adapted from CPW's code in cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper
     *
     * @param message     The message to send
     * @param dimensionId The dimension id to target
     */
    public void sendToDimension(AbstractPacket message, int dimensionId) {
        this.channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGET).set(FMLOutboundHandler.OutboundTarget.DIMENSION);
        this.channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGETARGS).set(dimensionId);
        this.channels.get(Side.SERVER).writeAndFlush(message);
    }

    /**
     * Send this message to the server.
     * <p/>
     * Adapted from CPW's code in cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper
     *
     * @param message The message to send
     */
    public void sendToServer(AbstractPacket message) {
        this.channels.get(Side.CLIENT).attr(FMLOutboundHandler.FML_MESSAGETARGET).set(FMLOutboundHandler.OutboundTarget.TOSERVER);
        this.channels.get(Side.CLIENT).writeAndFlush(message);
    }
}
import static org.jglrxavpok.mods.customai.common.CustomAIHelper.registerAI;
import java.util.ArrayList;
import java.util.Iterator;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.Mod.EventHandler;
import cpw.mods.fml.common.Mod.Instance;
import cpw.mods.fml.common.SidedProxy;
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.event.FMLServerStartingEvent;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.network.IGuiHandler;
import cpw.mods.fml.common.network.NetworkRegistry;
import cpw.mods.fml.common.registry.GameRegistry;
import net.minecraft.command.CommandException;
import net.minecraft.command.server.CommandSummon;
import net.minecraft.entity.ai.EntityAIArrowAttack;
import net.minecraft.entity.ai.EntityAIAttackOnCollide;
import net.minecraft.entity.ai.EntityAIAvoidEntity;
import net.minecraft.entity.ai.EntityAIBase;
import net.minecraft.entity.ai.EntityAIBeg;
import net.minecraft.entity.ai.EntityAIBreakDoor;
import net.minecraft.entity.ai.EntityAIControlledByPlayer;
import net.minecraft.entity.ai.EntityAICreeperSwell;
import net.minecraft.entity.ai.EntityAIDefendVillage;
import net.minecraft.entity.ai.EntityAIDoorInteract;
import net.minecraft.entity.ai.EntityAIEatGrass;
import net.minecraft.entity.ai.EntityAIFleeSun;
import net.minecraft.entity.ai.EntityAIFollowGolem;
import net.minecraft.entity.ai.EntityAIFollowOwner;
import net.minecraft.entity.ai.EntityAIFollowParent;
import net.minecraft.entity.ai.EntityAIHurtByTarget;
import net.minecraft.entity.ai.EntityAILeapAtTarget;
import net.minecraft.entity.ai.EntityAILookAtTradePlayer;
import net.minecraft.entity.ai.EntityAILookAtVillager;
import net.minecraft.entity.ai.EntityAILookIdle;
import net.minecraft.entity.ai.EntityAIMate;
import net.minecraft.entity.ai.EntityAIMoveIndoors;
import net.minecraft.entity.ai.EntityAIMoveThroughVillage;
import net.minecraft.entity.ai.EntityAIMoveTowardsRestriction;
import net.minecraft.entity.ai.EntityAIMoveTowardsTarget;
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
import net.minecraft.entity.ai.EntityAIOcelotAttack;
import net.minecraft.entity.ai.EntityAIOcelotSit;
import net.minecraft.entity.ai.EntityAIOpenDoor;
import net.minecraft.entity.ai.EntityAIOwnerHurtByTarget;
import net.minecraft.entity.ai.EntityAIOwnerHurtTarget;
import net.minecraft.entity.ai.EntityAIPanic;
import net.minecraft.entity.ai.EntityAIPlay;
import net.minecraft.entity.ai.EntityAIRestrictOpenDoor;
import net.minecraft.entity.ai.EntityAIRestrictSun;
import net.minecraft.entity.ai.EntityAIRunAroundLikeCrazy;
import net.minecraft.entity.ai.EntityAISit;
import net.minecraft.entity.ai.EntityAISwimming;
import net.minecraft.entity.ai.EntityAITarget;
import net.minecraft.entity.ai.EntityAITargetNonTamed;
import net.minecraft.entity.ai.EntityAITempt;
import net.minecraft.entity.ai.EntityAITradePlayer;
import net.minecraft.entity.ai.EntityAIVillagerMate;
import net.minecraft.entity.ai.EntityAIWander;
import net.minecraft.entity.ai.EntityAIWatchClosest;
import net.minecraft.entity.ai.EntityAIWatchClosest2;
import net.minecraft.entity.monster.EntityCreeper;
import net.minecraft.entity.monster.EntitySkeleton;
import net.minecraft.entity.monster.EntityWitch;
import net.minecraft.entity.monster.EntityZombie;
import net.minecraft.entity.passive.EntityChicken;
import net.minecraft.entity.passive.EntityCow;
import net.minecraft.entity.passive.EntityPig;
import net.minecraft.entity.passive.EntitySheep;
import net.minecraft.entity.passive.EntityWolf;
import net.minecraft.init.Blocks;
import net.minecraft.init.Items;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.JsonToNBT;
import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTException;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.util.IChatComponent;
import net.minecraft.util.StatCollector;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.CommandEvent;
import org.jglrxavpok.mods.customai.ai.ArrowProjectile;
import org.jglrxavpok.mods.customai.ai.EntityAIFleeSunEvenNotBurning;
import org.jglrxavpok.mods.customai.ai.EntityAIFollowEntity;
import org.jglrxavpok.mods.customai.ai.EntityAIRangeAttack;
import org.jglrxavpok.mods.customai.ai.EntityAITeleportRandomly;
import org.jglrxavpok.mods.customai.ai.Projectile;
import org.jglrxavpok.mods.customai.ai.WitherSkullProjectile;
import org.jglrxavpok.mods.customai.common.BlockAIEmitter;
import org.jglrxavpok.mods.customai.common.CommandTestForMod;
import org.jglrxavpok.mods.customai.common.ConfigHandler;
import org.jglrxavpok.mods.customai.common.CustomAIGuiHandler;
import org.jglrxavpok.mods.customai.common.CustomAIHelper;
import org.jglrxavpok.mods.customai.common.EntityEvents;
import org.jglrxavpok.mods.customai.common.Proxy;
import org.jglrxavpok.mods.customai.common.TileEntityAIEmitter;
import org.jglrxavpok.mods.customai.common.aifactory.EntityAIFactory;
import org.jglrxavpok.mods.customai.common.aifactory.EntityAIVanillaWorker;
import org.jglrxavpok.mods.customai.common.aifactory.EntityCustomAIAddedWorker;
import org.jglrxavpok.mods.customai.items.AICopierItem;
import org.jglrxavpok.mods.customai.items.AwesomeAIRewriterItem;
import org.jglrxavpok.mods.customai.json.JSONObject;
import org.jglrxavpok.mods.customai.netty.AbstractPacket;
import org.jglrxavpok.mods.customai.netty.PacketPipeline;
package org.jglrxavpok.mods.customai;





/**
 * Version number changed automatically by Gradle
 * @author jglrxavpok
 *
 */
@Mod(modid=ModCustomAI.MODID, name = "Custom AI", version = "@@MOD.VERSION@@")
public class ModCustomAI
{

    public static final PacketPipeline packetPipeline = new PacketPipeline("org.jglrxavpok.mods.customai.netty", AbstractPacket.class);
    public static final String MODID = "customai";
    private IGuiHandler guiHandler = new CustomAIGuiHandler();
    public static ConfigHandler config;
    
    @Instance(MODID)
    public static ModCustomAI instance;
    
    @SidedProxy(clientSide="org.jglrxavpok.mods.customai.client.ClientProxy", serverSide="org.jglrxavpok.mods.customai.common.Proxy")
    public static Proxy proxy;
    
    public static BlockAIEmitter aiEmitterBlock;
    public static AwesomeAIRewriterItem rewriterItem;
    public static AICopierItem copierItem;
    
    @SuppressWarnings({ "unchecked", "rawtypes" })
    @EventHandler
    public void preInit(FMLPreInitializationEvent event)
    {
        registerAI(EntityAISwimming.class, "Swim",false,0);
        registerAI(EntityAICreeperSwell.class, "Creeper swell",false,0);
        registerAI(EntityAIAvoidEntity.class, "Avoid entity",false,1);
        registerAI(EntityAIAttackOnCollide.class, "Attack by collision",false,1);
        registerAI(EntityAILookIdle.class, "Look idle",false,0);
        registerAI(EntityAINearestAttackableTarget.class, "Attack nearest target",true,0,EntityAITarget.class);
        registerAI(EntityAIHurtByTarget.class, "Hurt by target",true,0,EntityAITarget.class);
        registerAI(EntityAIArrowAttack.class, "Special range attack",false,0);
        registerAI(EntityAIBeg.class, "Beg for bone",false,0);
        registerAI(EntityAIBreakDoor.class, "Breaks doors",false,0,EntityAIDoorInteract.class);
        registerAI(EntityAIControlledByPlayer.class, "Controlled by player",false,0);
        registerAI(EntityAIDefendVillage.class, "Defend village",true,0);
        registerAI(EntityAIEatGrass.class, "Eat grass",false,0);
        registerAI(EntityAIFleeSun.class, "Flee sun",false,0);
        registerAI(EntityAIFollowGolem.class, "Follow Golem",false,0);
        registerAI(EntityAIFollowOwner.class, "Follow Owner",false,0);
        registerAI(EntityAIFollowParent.class, "Follow Parent",false,0);
        registerAI(EntityAILeapAtTarget.class, "Leap when attacking",false,0);
        registerAI(EntityAILookAtTradePlayer.class, "Look at trading player",false,0,EntityAIWatchClosest.class);
        registerAI(EntityAILookAtVillager.class, "Look at villager",false,0);
        registerAI(EntityAIMate.class, "Mating",false,0);
        registerAI(EntityAIPanic.class,"Panic",false,0);
        registerAI(EntityAITempt.class,"Tempted by item",false,0);
        registerAI(EntityAIMoveIndoors.class,"Move indoors",false,0);
        registerAI(EntityAIMoveThroughVillage.class,"Move through village",false,0);
        registerAI(EntityAIMoveTowardsRestriction.class,"Move towards' restriction",false,0);
        registerAI(EntityAIMoveTowardsTarget.class,"Move towards target",false,0);
        registerAI(EntityAIOcelotAttack.class, "Ocelot's attack",false,1);
        registerAI(EntityAIOcelotSit.class, "Ocelot sitting",false,0);
        registerAI(EntityAIOpenDoor.class, "Open doors",false,0,EntityAIDoorInteract.class);
        registerAI(EntityAIOwnerHurtTarget.class, "On owner hurting target",true,0,EntityAITarget.class);
        registerAI(EntityAIOwnerHurtByTarget.class, "On owner hurt",true,0,EntityAITarget.class);
        registerAI(EntityAIPlay.class, "Play",false,0);
        registerAI(EntityAIRestrictOpenDoor.class, "Open door restriction",false,0);
        registerAI(EntityAIRestrictSun.class, "Sun restriction",false,0);
        registerAI(EntityAIRunAroundLikeCrazy.class, "Run around like crazy",false,0);
        registerAI(EntityAISit.class, "Sit",false,0);
        registerAI(EntityAITargetNonTamed.class, "Target when non-tamed",true,0);
        registerAI(EntityAITradePlayer.class, "Trade with player",false,0);
        registerAI(EntityAIVillagerMate.class, "Villager mating",false,0);
        registerAI(EntityAIWander.class, "Wander",false,0);
        registerAI(EntityAIWatchClosest.class, "Watch closest entity",false,0);
        registerAI(EntityAIWatchClosest2.class, "Watch closest entity (2)",false,0,EntityAIWatchClosest.class);
        
        // =============================
        // Start of custom AI
        // =============================
        registerAI(EntityAIFleeSunEvenNotBurning.class, "Flee sun even not burning",false,0);