content: package thebetweenlands.common.entity.mobs;
  
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
  
  import javax.annotation.Nullable;
  
  import com.google.common.base.Predicate;
  
  import net.minecraft.entity.Entity;
  import net.minecraft.entity.EntityCreature;
  import net.minecraft.entity.EntityLivingBase;
  import net.minecraft.entity.IEntityLivingData;
  import net.minecraft.entity.SharedMonsterAttributes;
  import net.minecraft.entity.ai.EntityAIBase;
  import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
  import net.minecraft.entity.ai.EntityAISwimming;
  import net.minecraft.entity.ai.RandomPositionGenerator;
  import net.minecraft.entity.passive.EntityVillager;
  import net.minecraft.entity.player.EntityPlayer;
  import net.minecraft.nbt.NBTTagCompound;
  import net.minecraft.network.datasync.DataParameter;
  import net.minecraft.network.datasync.DataSerializers;
  import net.minecraft.network.datasync.EntityDataManager;
  import net.minecraft.pathfinding.Path;
  import net.minecraft.pathfinding.PathNavigate;
  import net.minecraft.pathfinding.PathNodeType;
  import net.minecraft.util.DamageSource;
  import net.minecraft.util.EntityDamageSourceIndirect;
  import net.minecraft.util.EntitySelectors;
  import net.minecraft.util.EnumFacing;
  import net.minecraft.util.EnumHand;
  import net.minecraft.util.ResourceLocation;
  import net.minecraft.util.SoundCategory;
  import net.minecraft.util.SoundEvent;
  import net.minecraft.util.math.AxisAlignedBB;
  import net.minecraft.util.math.BlockPos;
  import net.minecraft.util.math.MathHelper;
  import net.minecraft.util.math.Vec3d;
  import net.minecraft.world.DifficultyInstance;
  import net.minecraft.world.EnumDifficulty;
  import net.minecraft.world.World;
  import thebetweenlands.api.entity.IEntityBL;
  import thebetweenlands.api.entity.IEntityPreventUnmount;
  import thebetweenlands.api.storage.ILocalStorage;
  import thebetweenlands.client.render.model.ControlledAnimation;
  import thebetweenlands.common.entity.ai.EntityAIAttackOnCollide;
  import thebetweenlands.common.entity.movement.FlightMoveHelper;
  import thebetweenlands.common.entity.projectiles.EntityBLArrow;
  import thebetweenlands.common.entity.projectiles.EntityChiromawDroppings;
  import thebetweenlands.common.item.tools.bow.EnumArrowType;
  import thebetweenlands.common.registries.LootTableRegistry;
  import thebetweenlands.common.registries.SoundRegistry;
  import thebetweenlands.common.world.storage.BetweenlandsWorldStorage;
  import thebetweenlands.common.world.storage.location.EnumLocationType;
  import thebetweenlands.common.world.storage.location.LocationChiromawMatriarchNest;
  import thebetweenlands.common.world.storage.location.LocationStorage;
  
  public class EntityChiromawMatriarch extends EntityFlyingMob implements IEntityBL, IEntityPreventUnmount {
  	private static final DataParameter<Boolean> IS_NESTING = EntityDataManager.createKey(EntityChiromawMatriarch.class, DataSerializers.BOOLEAN);
  	private static final DataParameter<Boolean> RETURN_TO_NEST = EntityDataManager.createKey(EntityChiromawMatriarch.class, DataSerializers.BOOLEAN);
  	private static final DataParameter<Boolean> IS_LANDING = EntityDataManager.createKey(EntityChiromawMatriarch.class, DataSerializers.BOOLEAN);
  	private static final DataParameter<Boolean> IS_SPINNING = EntityDataManager.createKey(EntityChiromawMatriarch.class, DataSerializers.BOOLEAN);
  	private static final DataParameter<Integer> TARGET_ENTITY_ID = EntityDataManager.createKey(EntityChiromawMatriarch.class, DataSerializers.VARINT);
  	
  	public int broodCount;
  	public double pickupHeight;
  	public int droppingTimer; // makes sure player is always dropped
  	@Nullable
  	private BlockPos boundOrigin;
  	public float previousSpinAngle, spinAngle;
  	public float animTime, prevAnimTime;
  	public float flapSpeed = 0.5f;
  	public int flapTicks;
  	public int landingAbortTime;
  	private boolean returnFast; //whether matriarch should return to nest fast, e.g. when player is near it
  
  	public final ControlledAnimation landingTimer = new ControlledAnimation(10);
  	public final ControlledAnimation nestingTimer = new ControlledAnimation(20);
  	public final ControlledAnimation spinningTimer = new ControlledAnimation(10);
  
  	public EntityChiromawMatriarch(World world) {
  		super(world);
  		setSize(1.75F, 2F);
  		setIsNesting(false);
  		experienceValue = 150;
  		moveHelper = new FlightMoveHelper(this);
  		setPathPriority(PathNodeType.WATER, -8F);
  		setPathPriority(PathNodeType.BLOCKED, -8.0F);
  		setPathPriority(PathNodeType.OPEN, 8.0F);
  		setPathPriority(PathNodeType.FENCE, -8.0F);
  	}
  
  	@Override
  	protected void entityInit() {
  		super.entityInit();
  		dataManager.register(IS_NESTING, false);
  		dataManager.register(RETURN_TO_NEST, false);
  		dataManager.register(IS_LANDING, false);
  		dataManager.register(IS_SPINNING, false);
  		dataManager.register(TARGET_ENTITY_ID, -1);
  	}
  
  	@Override
  	protected void initEntityAI() {
  		tasks.addTask(0, new EntityAISwimming(this));
  		tasks.addTask(1, new EntityChiromawMatriarch.AIPickUpAndDropAttack(this, 2.0D, true));
  		tasks.addTask(2, new EntityChiromawMatriarch.AIReturnToNest(this, 1.25D, 2.15D));
  		tasks.addTask(3, new EntityChiromawMatriarch.AIMoveRandom(this));
  		tasks.addTask(4, new EntityChiromawMatriarch.AIChangeNest(this));
  		tasks.addTask(5, new EntityChiromawMatriarch.AIPoop(this));
  		targetTasks.addTask(1, new AIFindNearestTarget<EntityPlayer>(this, EntityPlayer.class, true, 16D).setUnseenMemoryTicks(160));
  		targetTasks.addTask(1, new AIFindNearestTarget<EntityVillager>(this, EntityVillager.class, true, 16D).setUnseenMemoryTicks(160));
  		targetTasks.addTask(1, new AIFindNearestTarget<EntityChiromawGreeblingRider>(this, EntityChiromawGreeblingRider.class, true, 16D).setUnseenMemoryTicks(160));
  	}
  
  	@Override
  	public void onUpdate() {
  		super.onUpdate();
  
  		if (!getEntityWorld().isRemote && getAttackTarget() == null) {
  			if (getEntityWorld().getDifficulty() == EnumDifficulty.PEACEFUL)
  				setDead();
  			if (getBroodCount() <= 0) {
  				setReturnToNest(!getReturnToNest());
  				setBroodCount(240);
  			}
  			if (getBroodCount() > 0)
  				setBroodCount(getBroodCount() - 1);
  		}
  
  		if (!getEntityWorld().isRemote && getAttackTarget() != null) {
  			if (getIsNesting())
  				setIsNesting(false);
  			if (getReturnToNest())
  				setReturnToNest(false);
  			if(getBroodCount() < 240)
  				setBroodCount(240);
  		}
  		
  		if(!this.world.isRemote) {
  			Entity target = this.getAttackTarget();
  			this.dataManager.set(TARGET_ENTITY_ID, target != null ? target.getEntityId() : -1);
  		}
  
  		if (isJumping && isInWater()) {
  			getMoveHelper().setMoveTo(posX, posY + 1, posZ, 1.0D);
  		}
  
  		if(!this.world.isRemote && this.ticksExisted % 20 == 0 && this.getBoundOrigin() != null) {
  			this.setBoundOrigin(this.world.getHeight(this.getBoundOrigin()));
  		}
  
  		if (!getEntityWorld().isRemote && getIsLanding()) {
  			if (landingAbortTime > 0 )
  				landingAbortTime--;
  			if (landingAbortTime <= 0) {
  				if (getIsLanding())
  					setIsLanding(false);
  				if (getReturnToNest())
  					setReturnToNest(false);
  				if (getBroodCount() < 240)
  					setBroodCount(240);
  			}
  		}
  		if (getBroodCount() > 0 && getAttackTarget() == null && getReturnToNest() && !getIsNesting()) {
  			if(getNestBox() != null && getEntityBoundingBox().intersects(getNestBox())) {
  				double d0 = getBoundOrigin().getX() + 0.5D - posX;
  				double d1 = getBoundOrigin().getY() - posY;
  				double d2 = getBoundOrigin().getZ() + 0.5D - posZ;
  				motionX += (Math.signum(d0) - motionX) * 0.0000000003D;
  				motionY += (Math.signum(d1) - motionY) * 0.03125D;
  				motionZ += (Math.signum(d2) - motionZ) * 0.0000000003D;
  
  				if (getEntityBoundingBox().minY > getNestBox().minY + 0.0625D && Math.sqrt(this.motionX * this.motionX + this.motionY * this.motionY + this.motionZ * this.motionZ) < 0.1D) {
  					if (!getEntityWorld().isRemote) {
  						if (!getIsLanding()) {
  							setIsLanding(true);
  							landingAbortTime = 40;
  						}
  					}
  				}
  				if (getEntityBoundingBox().minY <= getNestBox().minY + 0.0625D) {
  					if (!getEntityWorld().isRemote) {
  						if(getIsLanding()) {
  							setIsLanding(false);
  							getEntityWorld().playSound(null, getPosition(), SoundRegistry.CHIROMAW_MATRIARCH_LAND, SoundCategory.HOSTILE, 0.5F, 1F + (getEntityWorld().rand.nextFloat() - getEntityWorld().rand.nextFloat()) * 0.8F);
  						}
  						if(!getIsNesting())
  							setIsNesting(true);
  						setPosition(getBoundOrigin().getX() + 0.5D, getBoundOrigin().getY(), getBoundOrigin().getZ() + 0.5D);
  					}
  				}
  			}
  		}
  
  		if(getEntityWorld().getBlockState(getPosition().down()).isSideSolid(getEntityWorld(), getPosition().down(), EnumFacing.UP)) {
  			if(!getIsLanding() && !getIsNesting())
  				getMoveHelper().setMoveTo(posX, posY + 2, posZ, 1.0D);
  		}
  
  		if (getIsSpinning()) {
  			if (getEntityWorld().isRemote) {
  				previousSpinAngle = spinAngle;
  				if (spinAngle <= 330F)
  					spinAngle += 30F;
  				if (spinAngle >= 360F)
  					spinAngle = 0F;
  			}
  
  			//motionX *= 000001F;
  			motionY += 0.05F;
  			//motionZ *= 000001F;
  		}
  
  		if(this.world.isRemote) {
  			nestingTimer.updateTimer();
  			landingTimer.updateTimer();
  			spinningTimer.updateTimer();
  
  			if(this.getIsNesting()) {
  				nestingTimer.increaseTimer();
  				landingTimer.decreaseTimer();
  				spinningTimer.decreaseTimer();
  			} else if(this.getIsLanding()) {
  				landingTimer.increaseTimer();
  				nestingTimer.decreaseTimer();
  				spinningTimer.decreaseTimer();
  			} else if(this.getIsSpinning()) {
  				spinningTimer.increaseTimer();
  				landingTimer.decreaseTimer();
  				nestingTimer.decreaseTimer();
  			} else {
  				landingTimer.decreaseTimer();
  				nestingTimer.decreaseTimer();
  				spinningTimer.decreaseTimer();
  			}
  		}
  	}
  
  	@Override
  	public EntityLivingBase getAttackTarget() {
  		if(this.world.isRemote) {
  			int targetId = this.dataManager.get(TARGET_ENTITY_ID);
  			if(targetId >= 0) {
  				Entity target = this.world.getEntityByID(targetId);
  				if(target instanceof EntityLivingBase) {
  					return (EntityLivingBase) target;
  				}
  			}
  		}
  		return super.getAttackTarget();
  	}
  	
  	@Override
  	public void addVelocity(double x, double y, double z) {
  		if (getIsNesting()) {
  			motionX = 0;
  			motionY += y;
  			motionZ = 0;
  			isAirBorne = false;
  		} else {
  			motionX += x;
  			motionY += y;
  			motionZ += z;
  			isAirBorne = true;
  		}
  	}
  
  	//moved outside of AI because it may be useful
  	private boolean homeisOccupied() {
  		if (getNestBox() != null) {
  			Entity entity = null;
  			List<Entity> entityList = getEntityWorld().getEntitiesWithinAABBExcludingEntity(this, getNestBox());
  			if (!entityList.isEmpty())
  				for (int entityCount = 0; entityCount < entityList.size(); entityCount++) {
  					entity = entityList.get(entityCount);
  					if (entity != null && entity instanceof EntityChiromawMatriarch)
  						return true;
  				}
  		}
  		return false;
  	}
  
  	@Override
  	public void onLivingUpdate() {
  		super.onLivingUpdate();
  		if (world.isRemote) {
  			this.flapTicks++;
  
  			if (!isSilent() && !getIsNesting()) {
  				float flapAngle1 = MathHelper.cos(this.flapTicks * this.flapSpeed);
  				float flapAngle2 = MathHelper.cos((this.flapTicks + 1) * this.flapSpeed);
  				if(flapAngle1 <= 0.3f && flapAngle2 > 0.3f) {
  					world.playSound(posX, posY, posZ, getFlySound(), getSoundCategory(), getIsLanding() ? 0.25F : 0.5F, getIsLanding() ? 1.5F + rand.nextFloat() * 0.3F : 0.8F + rand.nextFloat() * 0.3F, false);
  				}
  			}
  
  			prevAnimTime = animTime;
  			float flaptimer = 0.079F;
  			if (getIsLanding())
  				animTime += flaptimer * 2F;
  			if (getIsNesting())
  				animTime = 0;
  			else
  				animTime += flaptimer;
  
  			if (isAIDisabled())
  				animTime = 0.5F;
  
  		}
  	}
  
  	@Override
  	protected void updateAITasks() {
  		if (getIsNesting()) {
  			if (!getEntityWorld().isRemote) {
  				if (!getEntityWorld().getBlockState(new BlockPos(posX, posY - 1, posZ)).isNormalCube()) {
  					setIsNesting(false);
  				} else if (getAttackTarget() != null) {
  					setIsNesting(false);
  				}
  			}
  		}
  	}
  
  	@Override
  	public void updatePassenger(Entity entity) {
  		super.updatePassenger(entity);
  		if (entity instanceof EntityLivingBase) {
  			double a = Math.toRadians(renderYawOffset);
  			double offSetX = -Math.sin(a) * 0.6D;
  			double offSetZ = Math.cos(a) * 0.6D;
  			entity.setPosition(posX - offSetX, posY - entity.height + MathHelper.sin((ticksExisted) * 0.5F) * 0.3F, posZ - offSetZ);
  		}
  	}
  
  	@Override
  	public boolean shouldRiderSit() {
  		return false;
  	}
  
  	@Override
  	public boolean canPassengerSteer() {
  		return false; //TODO different rider cases
  	}
  
  	public boolean getReturnToNest() {
  		return dataManager.get(RETURN_TO_NEST);
  	}
  
  	public void setReturnToNest(boolean broody) {
  		dataManager.set(RETURN_TO_NEST, broody);
  	}
  
  	public boolean getIsLanding() {
  		return dataManager.get(IS_LANDING);
  	}
  
  	public void setIsLanding(boolean landing) {
  		dataManager.set(IS_LANDING, landing);
  	}
  
  	public void setBroodCount(int count) {
  		broodCount = count;
  	}
  
  	public int getBroodCount() {
  		return broodCount;
  	}
  
  	public void setDroppingTimer(int count) {
  		droppingTimer = count;
  	}
  
  	public int getDroppingTimer() {
  		return droppingTimer;
  	}
  
  	public boolean getIsNesting() {
  		return dataManager.get(IS_NESTING);
  	}
  
  	public void setIsNesting(boolean nesting) {
  		dataManager.set(IS_NESTING, nesting);
  	}
  
  	public boolean getIsSpinning() {
  		return dataManager.get(IS_SPINNING);
  	}
  
  	public void setIsSpinning(boolean spinning) {
  		dataManager.set(IS_SPINNING, spinning);
  	}
  
  	@Override
  	protected ResourceLocation getLootTable() {
  		return LootTableRegistry.CHIROMAW_MATRIARCH;
  	}
  
  	@Override
  	protected SoundEvent getAmbientSound() {
  		return SoundRegistry.CHIROMAW_MATRIARCH_LIVING;
  	}
  
  	@Nullable
  	@Override
  	protected SoundEvent getHurtSound(DamageSource source) {
  		return SoundRegistry.CHIROMAW_MATRIARCH_HURT;
  	}
  
  	@Override
  	protected SoundEvent getDeathSound() {
  		return SoundRegistry.CHIROMAW_MATRIARCH_DEATH;
  	}
  
  	protected SoundEvent getFlySound() {
  		return SoundRegistry.CHIROMAW_MATRIARCH_FLAP;
  	}
  
  	@Override
  	protected void applyEntityAttributes() {
  		super.applyEntityAttributes();
  		getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(200.0D);
  		getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE).setBaseValue(64.0D);
  		getEntityAttribute(SharedMonsterAttributes.ATTACK_DAMAGE).setBaseValue(5.0D);
  		getEntityAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).setBaseValue(0.095D);
  	}
  
  	@Override
  	public boolean attackEntityAsMob(Entity entityIn) {
  		return EntityAIAttackOnCollide.useStandardAttack(this, entityIn);
  	}
  
  	@Override
  	@SuppressWarnings("rawtypes")
  	public boolean canAttackClass(Class entity) {
  		return EntityChiromawMatriarch.class != entity;
  	}
  
  	@Override
  	public int getMaxSpawnedInChunk() {
  		return 1;
  	}
  
  	@Override
  	public float getBlockPathWeight(BlockPos pos) {
  		return 0.5F;
  	}
  
  	@Override
  	protected boolean isValidLightLevel() {
  		return true;
  	}
  
  	@Override
  	protected boolean canDespawn() {
  		return false;
  	}
  
  	@Override
  	@Nullable
  	public IEntityLivingData onInitialSpawn(DifficultyInstance difficulty, @Nullable IEntityLivingData livingdata) {
  		if(!world.isRemote)
  			setBoundOrigin(getPosition());
  		return livingdata;
  	}
  
  	@Override
  	public void readEntityFromNBT(NBTTagCompound compound) {
  		super.readEntityFromNBT(compound);
  		if (compound.hasKey("BoundX")) 
  			boundOrigin = new BlockPos(compound.getInteger("BoundX"), compound.getInteger("BoundY"), compound.getInteger("BoundZ"));
  		setBroodCount(compound.getInteger("BroodCount"));
  	}
  
  	@Override
  	public void writeEntityToNBT(NBTTagCompound compound) {
  		super.writeEntityToNBT(compound);
  		if (boundOrigin != null) {
  			compound.setInteger("BoundX", boundOrigin.getX());
  			compound.setInteger("BoundY", boundOrigin.getY());
  			compound.setInteger("BoundZ", boundOrigin.getZ());
  		}
  		compound.setInteger("BroodCount", getBroodCount());
  	}
  
  	@Override
  	public boolean attackEntityFrom(DamageSource source, float damage) {
  		if (source.equals(DamageSource.IN_WALL) || source.equals(DamageSource.DROWN))
  			return false;
  
  		if (source instanceof EntityDamageSourceIndirect) {
  			if (source.getTrueSource() == this)
  				return false;
  			if (source.getTrueSource() instanceof EntityLivingBase) {
  				setRevengeTarget((EntityLivingBase) source.getTrueSource());
  			}
  		}
  
  		if (source.getTrueSource() instanceof EntityPlayer)
  			if (isBeingRidden() && isPassenger(source.getTrueSource()))
  				if(!getEntityWorld().isRemote)
  					setDroppingTimer(0);
  
  		return super.attackEntityFrom(source, damage);
  	}
  
  	@Override
  	public boolean isInRangeToRenderDist(double distance) {
  		return distance < 16384 * getRenderDistanceWeight();
  	}
  
  	@Nullable
  	public BlockPos getBoundOrigin() {
  		return boundOrigin;
  	}
  
  	public void setBoundOrigin(@Nullable BlockPos boundOriginIn) {
  		boundOrigin = boundOriginIn;
  	}
  
  	@Nullable
  	public AxisAlignedBB getNestBox() {
  		return boundOrigin != null ? new AxisAlignedBB(boundOrigin, boundOrigin.up(5)).grow(0.0625D, 0F, 0.0625D) : null;
  	}
  
  	public class AIPoop extends EntityAIBase {
  		private final EntityChiromawMatriarch largeChiromaw;
  
  		public AIPoop(EntityChiromawMatriarch large_chiromaw) {
  			setMutexBits(0);
  			largeChiromaw = large_chiromaw;
  		}
  
  		@Override
  		public boolean shouldExecute() {
  			return !largeChiromaw.getIsNesting() && largeChiromaw.rand.nextInt(10) == 0; // frequency of random pooping
  		}
  
  		@Override
  		public boolean shouldContinueExecuting() {
  			return false;
  		}
  
  		@Override
  		public void updateTask() {
  			checkForPoopTarget();
  		}
  
  		private void checkForPoopTarget() {
  			int distanceToSurface = MathHelper.floor(largeChiromaw.posY) - largeChiromaw.world.getHeight(new BlockPos(largeChiromaw)).getY();
  			List<BlockPos> placeToPoop = new ArrayList<>();
  			if (distanceToSurface >= 16) {
  				AxisAlignedBB underBox = largeChiromaw.getEntityBoundingBox().grow(0.625D, distanceToSurface, 0.625D).offset(0, -distanceToSurface / 2.0D, 0);
  				if (!getPoopTarget(world, underBox).isEmpty()) {
  					EntityPlayer player = getPoopTarget(world, underBox).get(0);
  					if (player != null) {
  						for (BlockPos posDrop : BlockPos.getAllInBox(player.getPosition().add(-1, 0, -1), player.getPosition().add(1, 2, 1)))
  							if (world.isAirBlock(posDrop) && world.canSeeSky(posDrop))
  								placeToPoop.add(posDrop);
  						if (!placeToPoop.isEmpty()) {
  							Collections.shuffle(placeToPoop);
  							BlockPos posPoop = placeToPoop.get(0);
  							EntityChiromawDroppings poopEntity = new EntityChiromawDroppings(world, largeChiromaw, posPoop.getX() + 0.5D, largeChiromaw.getPosition().getY(), posPoop.getZ() + 0.5D);
  							world.spawnEntity(poopEntity);
  							largeChiromaw.getEntityWorld().playSound(null, largeChiromaw.getPosition(), SoundRegistry.CHIROMAW_MATRIARCH_POOP, SoundCategory.HOSTILE, 0.5F, 1F + (largeChiromaw.getEntityWorld().rand.nextFloat() - largeChiromaw.getEntityWorld().rand.nextFloat()) * 0.8F);
  						}
  					}
  				}
  			}
  		}
  
  		public List<EntityPlayer> getPoopTarget(World world, AxisAlignedBB underBox) {
  			return world.<EntityPlayer>getEntitiesWithinAABB(EntityPlayer.class, underBox, EntitySelectors.IS_ALIVE);
  		}
  	}
  
  	class AIFindNearestTarget<T extends EntityLivingBase> extends EntityAINearestAttackableTarget<T> {
  		protected double baseRange;
  		protected double revengeRange;
  
  		public AIFindNearestTarget(EntityCreature creature, Class<T> classTarget, boolean checkSight, double rangeIn) {
  			super(creature, classTarget, checkSight);
  			baseRange = revengeRange = rangeIn;
  		}
  
  		public AIFindNearestTarget(EntityCreature creature, Class<T> classTarget, int chance, boolean checkSight, boolean onlyNearby, @Nullable final Predicate <? super T > targetSelector) {
  			super(creature, classTarget, chance, checkSight, onlyNearby, targetSelector);
  		}
  
  		@Override
  		public boolean shouldExecute() {
  			if (super.shouldExecute()) {
  				if (targetEntity != null) {
  					double distance = taskOwner.getDistanceSq(targetEntity);
  					if (distance <= 256.0D)
  						taskOwner.getEntityWorld().playSound(null, taskOwner.getPosition(), SoundRegistry.CHIROMAW_MATRIARCH_ROAR, SoundCategory.HOSTILE, 2F, 1.5F);
  				}
  				return true;
  			}
  			return false;
  		}
  
  		@Override
  		protected AxisAlignedBB getTargetableArea(double targetDistance) {
  			return taskOwner.getEntityBoundingBox().grow(targetDistance, targetDistance, targetDistance);
  		}
  
  		@Override
  		protected double getTargetDistance() {
  			if(getRevengeTarget() != null) {
  				setAttackTarget(getRevengeTarget());
  				revengeRange = getDistance(getRevengeTarget());
  			}
  			return Math.max(baseRange, revengeRange);
  		}
  	}
  
  	class AIPickUpAndDropAttack extends EntityAIBase {
  		World world;
  		protected int attackTick;
  		double speedTowardsTarget;
  		boolean longMemory;
  		Path path;
  		private int delayCounter;
  		private double targetX;
  		private double targetY;
  		private double targetZ;
  		protected final int attackInterval = 20;
  		private int failedPathFindingPenalty = 0;
  		private boolean canPenalize = false;
  		private final EntityChiromawMatriarch largeChiromaw;
  		float rotation;
  
  		public AIPickUpAndDropAttack(EntityChiromawMatriarch large_chiromaw, double speedIn, boolean useLongMemory) {
  			largeChiromaw = large_chiromaw;
  			world = largeChiromaw.world;
  			speedTowardsTarget = speedIn;
  			longMemory = useLongMemory;
  			setMutexBits(1);
  		}
  
  		@Override
  		public boolean shouldExecute() {
  			EntityLivingBase entitylivingbase = largeChiromaw.getAttackTarget();
  			//if (largeChiromaw.getReturnToNest())
  			//	return false;
  			if (entitylivingbase == null)
  				return false;
  			else if (!entitylivingbase.isEntityAlive())
  				return false;
  			else {
  				if (canPenalize) {
  					if (--delayCounter <= 0) {
  						path = largeChiromaw.getNavigator().getPathToEntityLiving(entitylivingbase);
  						delayCounter = 4 + largeChiromaw.getRNG().nextInt(7);
  						return path != null;
  					} else
  						return true;
  				}
  				path = largeChiromaw.getNavigator().getPathToEntityLiving(entitylivingbase);
  				if (path != null)
  					return true;
  				else
  					return getAttackReachSqr(entitylivingbase) >= largeChiromaw.getDistanceSq(entitylivingbase.posX, entitylivingbase.getEntityBoundingBox().minY, entitylivingbase.posZ);
  			}
  		}
  
  		@Override
  		public boolean shouldContinueExecuting() {
  			EntityLivingBase entitylivingbase = largeChiromaw.getAttackTarget();
  			if (entitylivingbase == null)
  				return false;
  			if (largeChiromaw.isBeingRidden())
  				return false;
  			if (!entitylivingbase.isEntityAlive())
  				return false;
  			if (!longMemory)
  				return !largeChiromaw.getNavigator().noPath();
  			if (!largeChiromaw.isWithinHomeDistanceFromPosition(new BlockPos(entitylivingbase)))
  				return false;
  			return !(entitylivingbase instanceof EntityPlayer) || !((EntityPlayer)entitylivingbase).isSpectator() && !((EntityPlayer)entitylivingbase).isCreative();
  		}
  
  		@Override
  		public void startExecuting() {
  			largeChiromaw.getNavigator().setPath(path, speedTowardsTarget);
  			delayCounter = 0;
  		}
  
  		@Override
  		public void resetTask() {
  			EntityLivingBase entitylivingbase = largeChiromaw.getAttackTarget();
  			if (entitylivingbase instanceof EntityPlayer && (((EntityPlayer)entitylivingbase).isSpectator() || ((EntityPlayer)entitylivingbase).isCreative()))
  				largeChiromaw.setAttackTarget((EntityLivingBase)null);
  			largeChiromaw.getNavigator().clearPath();
  			rotation = 0;
  			setIsSpinning(false);
  		}
  
  		@Override
  		public void updateTask() {
  			EntityLivingBase entitylivingbase = largeChiromaw.getAttackTarget();
  			if(entitylivingbase == null) {
  				return;
  			}
  			if(!largeChiromaw.getIsSpinning())
  				largeChiromaw.getLookHelper().setLookPositionWithEntity(entitylivingbase, 30.0F, 30.0F);
  			double distToEnemySqr = largeChiromaw.getDistanceSq(entitylivingbase.posX, entitylivingbase.getEntityBoundingBox().minY, entitylivingbase.posZ);
  			--delayCounter;
  			if (!largeChiromaw.isBeingRidden()) {
  				if ((longMemory || largeChiromaw.getEntitySenses().canSee(entitylivingbase)) && delayCounter <= 0 && (targetX == 0.0D && targetY == 0.0D && targetZ == 0.0D || entitylivingbase.getDistanceSq(targetX, targetY, targetZ) >= 1.0D || largeChiromaw.getRNG().nextFloat() < 0.05F)) {
  					targetX = entitylivingbase.posX;
  					targetY = entitylivingbase.getEntityBoundingBox().minY;
  					targetZ = entitylivingbase.posZ;
  					delayCounter = 4 + largeChiromaw.getRNG().nextInt(7);
  
  					if (canPenalize) {
  						delayCounter += failedPathFindingPenalty;
  						if (largeChiromaw.getNavigator().getPath() != null) {
  							net.minecraft.pathfinding.PathPoint finalPathPoint = largeChiromaw.getNavigator().getPath().getFinalPathPoint();
  							if (finalPathPoint != null && entitylivingbase.getDistanceSq(finalPathPoint.x, finalPathPoint.y, finalPathPoint.z) < 1)
  								failedPathFindingPenalty = 0;
  							else
  								failedPathFindingPenalty += 10;
  						}
  						else
  							failedPathFindingPenalty += 10;
  					}
  					if (distToEnemySqr > 1024.0D)
  						delayCounter += 10;
  					else if (distToEnemySqr > 256.0D)
  						delayCounter += 5;
  					if (!largeChiromaw.getNavigator().tryMoveToEntityLiving(entitylivingbase, speedTowardsTarget))
  						delayCounter += 15;
  				}
  				if (largeChiromaw.rand.nextBoolean())
  					checkAndPerformDropAttack(entitylivingbase, distToEnemySqr);
  				else
  					checkAndPerformSpinAttack(entitylivingbase, distToEnemySqr);
  			}
  
  			attackTick = Math.max(attackTick - 1, 0);
  
  			if (largeChiromaw.isBeingRidden()) {
  				if (entitylivingbase != null && !world.isAirBlock(largeChiromaw.getPosition().down(3)) || largeChiromaw.getPosition().getY() < largeChiromaw.pickupHeight + 16D) {
  					largeChiromaw.getNavigator().clearPath();
  					Vec3d vec3d = RandomPositionGenerator.findRandomTargetBlockAwayFrom(largeChiromaw, 16, 10, new Vec3d(largeChiromaw.getBoundOrigin().getX() + 0.5D, largeChiromaw.getBoundOrigin().getY() + 0.5D, largeChiromaw.getBoundOrigin().getZ() + 0.5D));
  					if(vec3d != null)
  						largeChiromaw.getNavigator().tryMoveToXYZ(vec3d.x, vec3d.y, vec3d.z, 2D);
  					largeChiromaw.motionY += 0.05D;
  				}
  
  				if (!world.isRemote &&  (largeChiromaw.posY >= largeChiromaw.pickupHeight + 16D || largeChiromaw.getDroppingTimer() <= 0 || !world.isRemote && world.isSideSolid(new BlockPos (MathHelper.floor(largeChiromaw.posX), MathHelper.floor(largeChiromaw.posY + 1D), MathHelper.floor(largeChiromaw.posZ)), EnumFacing.DOWN))) {
  					largeChiromaw.setReturnToNest(true);
  					largeChiromaw.removePassengers();
  					largeChiromaw.getEntityWorld().playSound(null, largeChiromaw.getPosition(), SoundRegistry.CHIROMAW_MATRIARCH_RELEASE, SoundCategory.HOSTILE, 0.5F, 1F + (largeChiromaw.getEntityWorld().rand.nextFloat() - largeChiromaw.getEntityWorld().rand.nextFloat()) * 0.8F);
  				}
  
  				if (largeChiromaw.getDroppingTimer() >= 0) {
  					if(attackTick <= 0) {
  						largeChiromaw.attackEntityAsMob(entitylivingbase);
  						attackTick = 20;
  					}
  				}
  
  				if (largeChiromaw.getDroppingTimer() >= 0)
  					largeChiromaw.setDroppingTimer(getDroppingTimer() - 1);
  			}
  
  			if(largeChiromaw.getIsSpinning()) {
  				rotation += 30;
  				if (rotation%30 == 0) {
  					Vec3d targetVector = new Vec3d(entitylivingbase.posX, entitylivingbase.posY, entitylivingbase.posZ);
  					Vec3d chiromawVectorToTarget = new Vec3d(largeChiromaw.posX, largeChiromaw.posY, largeChiromaw.posZ).subtract(targetVector);
  					double height = chiromawVectorToTarget.y;
  					double distance = chiromawVectorToTarget.length();
  					double angle = MathHelper.atan2(height, distance) * (180D / Math.PI);
  					EntityBLArrow arrow = new EntityBLArrow(world, largeChiromaw);
  					arrow.setType(EnumArrowType.CHIROMAW_BARB);
  					arrow.shoot(largeChiromaw, (float)angle, rotation, 1.5F, 0.5F, 0.5F);
  					largeChiromaw.getEntityWorld().playSound(null, largeChiromaw.getPosition(), SoundRegistry.CHIROMAW_MATRIARCH_BARB_FIRE, SoundCategory.HOSTILE, 0.5F, 1F + (largeChiromaw.getEntityWorld().rand.nextFloat() - largeChiromaw.getEntityWorld().rand.nextFloat()) * 0.8F);
  					world.spawnEntity(arrow);
  				}
  				if(rotation >= 720) {
  					rotation = 0;
  					setIsSpinning(false);
  				}
  			}
  		}
  
  		private void checkAndPerformSpinAttack(EntityLivingBase entitylivingbase, double distToEnemySqr) {
  			if (distToEnemySqr >= 36.0D && distToEnemySqr <= 100.0D && attackTick <= 0) {
  				largeChiromaw.getEntityWorld().playSound(null, largeChiromaw.getPosition(), SoundRegistry.CHIROMAW_MATRIARCH_ROAR, SoundCategory.HOSTILE, 1F, 1F + (largeChiromaw.getEntityWorld().rand.nextFloat() - largeChiromaw.getEntityWorld().rand.nextFloat()) * 0.8F);
  				attackTick = 40;
  				largeChiromaw.setIsSpinning(true);
  			}
  			else
  				return;
  		}
  
  		protected void checkAndPerformDropAttack(EntityLivingBase enemy, double distToEnemySqr) {
  			double attackReachSq = getAttackReachSqr(enemy);
  			if (distToEnemySqr <= attackReachSq && attackTick <= 0)  {
  				attackTick = 20;
  				largeChiromaw.jump();
  				largeChiromaw.swingArm(EnumHand.MAIN_HAND);
  				largeChiromaw.attackEntityAsMob(enemy);
  				largeChiromaw.getEntityWorld().playSound(null, largeChiromaw.getPosition(), SoundRegistry.CHIROMAW_MATRIARCH_GRAB, SoundCategory.HOSTILE, 0.5F, 1F + (largeChiromaw.getEntityWorld().rand.nextFloat() - largeChiromaw.getEntityWorld().rand.nextFloat()) * 0.8F);
  				enemy.startRiding(largeChiromaw, true);
  				largeChiromaw.pickupHeight = largeChiromaw.posY;
  				largeChiromaw.setDroppingTimer(120);
  			}
  		}
  
  		protected double getAttackReachSqr(EntityLivingBase attackTarget)  {
  			return (double)(largeChiromaw.width * 2.0F * largeChiromaw.width * 2.0F + attackTarget.width);
  		}
  	}
  
  	class AIMoveRandom extends EntityAIBase {
  		private final EntityChiromawMatriarch largeChiromaw;
  
  		public AIMoveRandom(EntityChiromawMatriarch large_chiromaw) {
  			setMutexBits(1);
  			largeChiromaw = large_chiromaw;
  		}
  
  		@Override
  		public boolean shouldExecute() {
  			return !largeChiromaw.getReturnToNest() && !largeChiromaw.getMoveHelper().isUpdating() && largeChiromaw.rand.nextInt(10) == 0;
  		}
  
  		@Override
  		public boolean shouldContinueExecuting() {
  			return false;
  		}
  
  		@Override
  		public void updateTask() {
  			BlockPos blockpos = largeChiromaw.getBoundOrigin();
  
  			if (blockpos == null) {
  				blockpos = new BlockPos(largeChiromaw);
  			}
  
  			for (int i = 0; i < 3; ++i) {
  				BlockPos blockpos1 = blockpos.add(largeChiromaw.rand.nextInt(33) - 16, largeChiromaw.rand.nextInt(17) - 8, largeChiromaw.rand.nextInt(33) - 16);
  
  				if (largeChiromaw.world.isAirBlock(blockpos1)) {
  					largeChiromaw.moveHelper.setMoveTo((double) blockpos1.getX() + 0.5D, (double) blockpos1.getY() + 0.5D, (double) blockpos1.getZ() + 0.5D, 3D);
  
  					if (largeChiromaw.getAttackTarget() == null) {
  						largeChiromaw.getLookHelper().setLookPosition((double) blockpos1.getX() + 0.5D, (double) blockpos1.getY() + 0.5D, (double) blockpos1.getZ() + 0.5D, 180.0F, 20.0F);
  					}
  					break;
  				}
  			}
  		}
  	}
  
  	class AIChangeNest extends EntityAIBase {
  		private final EntityChiromawMatriarch largeChiromaw;
  
  		protected float maxRangeSq = 180 * 180;
  
  		private int checkTimer = 0;
  
  		private int idleChangeCounter = 0;
  
  		public AIChangeNest(EntityChiromawMatriarch large_chiromaw) {
  			setMutexBits(0);
  			largeChiromaw = large_chiromaw;
  		}
  
  		private Vec3d getCenter(AxisAlignedBB aabb) {
  			return new Vec3d(
  					aabb.minX + (aabb.maxX - aabb.minX) * 0.5D,
  					aabb.minY + (aabb.maxY - aabb.minY) * 0.5D,
  					aabb.minZ + (aabb.maxZ - aabb.minZ) * 0.5D);
  		}
  
  		@Override
  		public boolean shouldExecute() {
  			if(!largeChiromaw.getReturnToNest() && largeChiromaw.getAttackTarget() == null && checkTimer-- <= 0) {
  				checkTimer = 10;
  
  				BetweenlandsWorldStorage worldStorage = BetweenlandsWorldStorage.forWorld(largeChiromaw.world);
  
  				List<LocationStorage> priorityNests = new ArrayList<>();
  				List<LocationStorage> otherNests = new ArrayList<>();
  
  				for(ILocalStorage localStorage : worldStorage.getLocalStorageHandler().getLoadedStorages()) {
  
  					if(localStorage instanceof LocationStorage  && localStorage.getBoundingBox() != null) {
  
  						LocationStorage location = (LocationStorage) localStorage;
  
  						Vec3d center = this.getCenter(location.getBoundingBox());
  
  						if(center.squareDistanceTo(largeChiromaw.posX, largeChiromaw.posY, largeChiromaw.posZ) < maxRangeSq && (location.getType() == EnumLocationType.FLOATING_ISLAND || location.getType() == EnumLocationType.CHIROMAW_MATRIARCH_NEST) &&
  								(largeChiromaw.rand.nextInt(15) == 0 || largeChiromaw.world.getEntitiesWithinAABB(this.largeChiromaw.getClass(), location.getBoundingBox()).isEmpty() /*Avoid already occupied places*/)) {
  							if(location instanceof LocationChiromawMatriarchNest && largeChiromaw.world.getClosestPlayer(center.x, center.y, center.z, 32, entity -> !((EntityPlayer) entity).isCreative() && !((EntityPlayer) entity).isSpectator()) != null) {
  								//Prioritise nests with players nearby
  								priorityNests.add(location);
  							} else {
  								//Prioritise nests over islands
  								if(location instanceof LocationChiromawMatriarchNest || largeChiromaw.rand.nextInt(3) == 0) {
  									otherNests.add(location);
  								}
  							}
  						}
  					}
  
  				}
  
  				LocationStorage nest = null;
  				if(!priorityNests.isEmpty()) {
  					Collections.shuffle(priorityNests);
  					nest = priorityNests.get(0);
  					largeChiromaw.returnFast = true;
  				} else if(!otherNests.isEmpty() && idleChangeCounter++ >= 8) {
  					idleChangeCounter = 0;
  					Collections.shuffle(otherNests);
  					nest = otherNests.get(0);
  					largeChiromaw.returnFast = false;
  				}
  
  				if(nest != null) {
  					BlockPos nestPos;
  					if(nest instanceof LocationChiromawMatriarchNest) {
  						nestPos = ((LocationChiromawMatriarchNest) nest).getNestPosition();
  						if(nestPos == null) {
  							nestPos = new BlockPos(this.getCenter(nest.getBoundingBox()));
  						}
  					} else {
  						AxisAlignedBB locationBB = nest.getBoundingBox();
  						if(locationBB != null) {
  							nestPos = new BlockPos(this.getCenter(locationBB));
  						} else {
  							nestPos = null;
  						}
  					}
  
  					if(nestPos != null) {
  						nestPos = largeChiromaw.world.getHeight(nestPos);
  
  						if(!nestPos.equals(largeChiromaw.getBoundOrigin())) {
  							largeChiromaw.setBoundOrigin(nestPos);
  							largeChiromaw.setIsNesting(false);
  							largeChiromaw.setReturnToNest(true);
  							return true;
  						}
  					}
  				}
  			}
  
  			return false;
  		}
  
  		@Override
  		public boolean shouldContinueExecuting() {
  			return false;
  		}
  	}
  
  	class AIReturnToNest extends EntityAIBase {
  		private final EntityChiromawMatriarch largeChiromaw;
  		protected double x;
  		protected double y;
  		protected double z;
  		private final double speed, speedHigh;
  		private int timeToRecalcPath;
  
  		public AIReturnToNest(EntityChiromawMatriarch large_chiromaw, double speedIn, double speedHighIn) {
  			largeChiromaw = large_chiromaw;
  			speed = speedIn;
  			speedHigh = speedHighIn;
  			setMutexBits(1);
  		}
  
  		@Override
  		public boolean shouldExecute() {
  			EntityLivingBase entitylivingbase = largeChiromaw.getAttackTarget();
  			if (entitylivingbase != null)
  				return false;
  			if (largeChiromaw.homeisOccupied())
  				return false;
  			if (largeChiromaw.getReturnToNest() && !largeChiromaw.getIsNesting()) {
  				Vec3d nestLocation = getNestPosition();
  
  				if (nestLocation == null) {
  					return false;
  				} else {
  					x = nestLocation.x;
  					y = nestLocation.y;
  					z = nestLocation.z;
  					return true;
  				}
  			}
  			return false;
  		}
  
  		@Nullable
  		protected Vec3d getNestPosition() {
  			BlockPos home = getBoundOrigin();
  			home = largeChiromaw.world.getHeight(home);
  			return new Vec3d(home.getX() + 0.5D, home.getY(), home.getZ() + 0.5D);
  		}
  
  		@Override
  		public void startExecuting() {
  			this.moveTowardsNest();
  		}
  
  		@Override
  		public boolean shouldContinueExecuting() {
  			return largeChiromaw.getReturnToNest() && !largeChiromaw.getIsNesting() && largeChiromaw.getAttackTarget() == null && !largeChiromaw.homeisOccupied();
  		}
  
  		@Override
  		public void updateTask() {
  			if (--this.timeToRecalcPath <= 0) {
  				if(!this.moveTowardsNest()) {
  					this.timeToRecalcPath = 40;
  				} else {
  					this.timeToRecalcPath = 3;
  				}
  			}
  		}
  
  		private boolean moveTowardsNest() {
  			if(largeChiromaw.getDistanceSq(x, y, z) < 6) {
  				largeChiromaw.getMoveHelper().setMoveTo(x, y, z, speed * 0.25f);
  				largeChiromaw.returnFast = false;
  				return true;
  			}
  
  			PathNavigate navigator = largeChiromaw.getNavigator();
  			if(!navigator.tryMoveToXYZ(x, y, z, largeChiromaw.returnFast ? speedHigh : speed)) {
  				Vec3d target = new Vec3d(x, y, z);
  				target = this.findNextPointTowards(12, 5, target);
  				if(target != null && !navigator.tryMoveToXYZ(target.x, target.y, target.z, largeChiromaw.returnFast ? speedHigh : speed)) {
  					largeChiromaw.getMoveHelper().setMoveTo(target.x, target.y, target.z, largeChiromaw.returnFast ? speedHigh : speed);
  				}
  				return false;
  			}
  			return true;
  		}
  
  		@Nullable
  		private Vec3d findNextPointTowards(int xz, int y, Vec3d target) {
  			Vec3d offset = target.subtract(largeChiromaw.getPositionVector()).normalize().scale(8 + largeChiromaw.rand.nextFloat() * 6);
  			Vec3d side = offset.crossProduct(new Vec3d(0, 1, 0)).normalize();
  			Vec3d up = side.crossProduct(offset).normalize();
  			return largeChiromaw.getPositionVector().add(offset).add(side.scale((largeChiromaw.rand.nextFloat() - 0.5f) * 8)).add(up.scale((largeChiromaw.rand.nextFloat() - 0.5f) * 8));
  		}
  	}
  
  	@Override
  	public boolean isUnmountBlocked(EntityPlayer rider) {
  		return !this.world.isRemote && this.getDroppingTimer() > 0;
  	}
  }
  